Policy Layer
About
Non-custodial spending controls for AI agent wallets — enforce limits, allowlists, and kill switches before transactions execute.
Details
- Author
- Unknown
- Categories
- Other, AI, Security
Jump to
Your AI agent just made its 47th Stripe charge of the day. Each one looked reasonable in isolation — $12 here, $35 there — but the cumulative total hit $4,200 before anyone noticed. The agent was doing exactly what it was told: processing orders. It just never stopped.
Adding spending controls to your MCP agent prevents exactly this scenario. MCP servers like Stripe, AWS, and Twilio give agents direct access to tools that cost real money. The agent doesn’t know it has a budget. The MCP server doesn’t enforce one. And thesystem prompt saying “don’t spend more than $500 per day”is a suggestion, not a constraint.
PolicyLayer solves this by sitting between the agent and the MCP server as a transparent proxy. Everytools/callrequest passes through it, gets evaluated against a YAML policy file, and is either forwarded or blocked. The agent doesn’t know PolicyLayer exists — same tools, same schemas, same interface.
┌──────────┐ ┌─────────────┐ ┌────────────┐ │ LLM/AI │──────>│ PolicyLayer │──────>│ MCP Server │ │ Client │<──────│ (proxy) │<──────│ (upstream) │ └──────────┘ └─────────────┘ └────────────┘ │ ┌────┴────┐ │ Policy │ │ Engine │ └────┬────┘ ┌────┴────┐ │ State │ │ Store │ └─────────┘
PolicyLayer proxies MCP traffic over HTTP. It interceptstools/callrequests, evaluates them against your policy, and returns a denial message if any rule fails. The state store persists counters across restarts so your daily spend caps survive process recycling.
Before writing policies, you need to know what tools are available. PolicyLayer connects to any registered MCP server, discovers its tools, and generates a commented YAML scaffold listing every tool with its parameters, grouped by category. It’s a starting point — everything is allowed by default until you add rules.
The most basic spending control is capping a single transaction. If your agent can callcreate_charge, you probably don’t want it creating $10,000 charges:
version: "1" description: "Stripe spending controls" tools: create_charge: rules: - name: "max single charge" conditions: - path: "args.amount" op: "lte" value: 50000 on_deny: "Single charge cannot exceed $500.00"
This rule checks theamountargument on everycreate_chargecall. If it exceeds 50000 (Stripe uses cents), the call is blocked and the agent receives the denial message. The agent can then decide what to do — ask the user for approval, split the transaction, or abandon the task.
The key detail: this check happens at the transport layer, before the request reaches Stripe. The charge is never created. There’s no refund to process, no failed payment to reconcile. This isdeterministic policy enforcement— the same input always produces the same result.
Per-transaction limits don’t prevent accumulation. An agent making 200 charges of $50 each will sail past a $500 single-charge limit while racking up $10,000 in total spend. You need cumulative tracking.
PolicyLayer handles this with stateful counters:
tools: create_charge: rules: - name: "max single charge" conditions: - path: "args.amount" op: "lte" value: 50000 on_deny: "Single charge cannot exceed $500.00" - name: "daily spend cap" conditions: - path: "state.create_charge.daily_spend" op: "lte" value: 1000000 on_deny: "Daily spending cap of $10,000.00 reached" state: counter: "daily_spend" window: "day" increment_from: "args.amount"
Thestateblock creates a counter calleddaily_spendthat resets at midnight UTC. On each allowedcreate_chargecall, the counter increments by whateverargs.amountis. Before the next call, the condition checks whether the cumulative total exceeds the limit.
Theincrement_fromfield is what makes this work for spending specifically. Instead of counting calls (the default), it sums the actual dollar amounts. A $50 charge increments by 5000, a $200 charge by 20000. When the running total would exceed 1000000 ($10,000), further charges are denied.
Counters persist in the state store. If you restart PolicyLayer, the daily total picks up where it left off. And the two-phase model means failed upstream calls don’t consume quota — if Stripe returns an error, the increment is rolled back.
Step 4: Restrict Currencies and Arguments
Spending controls aren’t just about amounts. You might want to restrict which currencies an agent can charge in, which regions it can operate in, or which products it can purchase:
- name: "allowed currencies" conditions: - path: "args.currency" op: "in" value: ["usd", "eur"] on_deny: "Only USD and EUR charges are permitted"
This uses theinoperator to check against a whitelist. You can combine multiple conditions in a single rule — they’re ANDed together:
- name: "safe charge" conditions: - path: "args.amount" op: "lte" value: 50000 - path: "args.currency" op: "in" value: ["usd", "eur"] on_deny: "Charge must be under $500 and in USD or EUR"
Both conditions must pass. If either fails, the entire call is denied.
Some tools should never be called by an agent, regardless of arguments. Deleting customers, dropping databases, removing infrastructure — these are human-only operations:
hide: - delete_customer - delete_product - delete_invoice tools: delete_subscription: rules: - name: "block subscription deletion" action: "deny" on_deny: "Subscription deletion is not permitted via AI agents"
There are two approaches here. Thehidelist removes tools from the agent’s view entirely — they’re stripped fromtools/listresponses, so the agent never knows they exist. This saves context window tokens and prevents the agent from even attempting the call.
For tools you want the agent to see but not use, useaction: "deny". The tool shows up intools/list, but any call is unconditionally blocked with the denial message.
Even with per-tool spending controls, you want a backstop. A global rate limit caps the total number of tool calls per time window across all tools:
"": rules: - name: "global rate limit" rate_limit: 60/minute
The""wildcard applies to every tool call. This preventsrunaway loopswhere an agent calls tools hundreds of times per minute, regardless of whether each individual call passes its specific rules. For more onrate limiting strategies, see our practical guide.
Route your Stripe MCP server through PolicyLayer — point your MCP client at the gateway URL with a per-person grant token, and the policy above runs on every call before it reaches Stripe:
{ "mcpServers": { "stripe": { "url": "https://proxy.policylayer.com/mcp/<server-uuid>/", "headers": { "Authorization": "Bearer <grant-token>" } } } }
You define and adjust the policy in thePolicyLayer dashboard— no local proxy to install or run.
The agent connects to PolicyLayer thinking it’s the Stripe MCP server. PolicyLayer forwards everything except policy violations.
Here’s the full policy combining all the rules above:
Click to expand the complete policy YAML
version: "1" description: "Stripe MCP server spending controls" hide: - delete_customer - delete_product - delete_invoice tools: create_charge: rules: - name: "max single charge" conditions: - path: "args.amount" op: "lte" value: 50000 on_deny: "Single charge cannot exceed $500.00" - name: "daily spend cap" conditions: - path: "state.create_charge.daily_spend" op: "lte" value: 1000000 on_deny: "Daily spending cap of $10,000.00 reached" state: counter: "daily_spend" window: "day" increment_from: "args.amount" - name: "allowed currencies" conditions: - path: "args.currency" op: "in" value: ["usd", "eur"] on_deny: "Only USD and EUR charges are permitted" create_refund: rules: - name: "refund amount cap" conditions: - path: "args.amount" op: "lte" value: 10000 on_deny: "Refunds over $100.00 require manual processing" - name: "daily refund count" rate_limit: 10/day on_deny: "Daily refund limit (10) reached" "": rules: - name: "global rate limit" rate_limit: 60/minute
Policies are hot-reloadable. Edit the policy in the dashboard while PolicyLayer is running and changes apply immediately — no restart, no dropped connections. This means you can tighten limits in response to observed behaviour without interrupting the agent.
PolicyLayer also validates policies before they go live, catching syntax errors, invalid operators, missing counters, and logical conflicts before they hit production.
When a call is denied, the agent receives a message like:
[POLICYLAYER POLICY DENIED] Daily spending cap of $10,000.00 reached
This is deliberate. The agent knowswhy*the call failed and can adapt its behaviour — inform the user, try a smaller amount, or wait until the window resets. It’s a feedback loop, not a silent failure.
The same pattern works for any MCP server that touches money or resources. AWS cost controls, Twilio message limits, database write caps, API call budgets — if the tool has arguments you can validate and calls you can count, PolicyLayer can enforce limits on it.
How do MCP spending controls persist across restarts?
PolicyLayer stores counter state in a persistent state store. When you restart PolicyLayer, daily spend totals, rate limit counters, and all other stateful tracking picks up exactly where it left off. No state is lost.
Not through PolicyLayer. Because spending controls are enforced at the transport layer — between the agent and the MCP server — the agent has no way to bypass them. The agent doesn’t even know PolicyLayer exists. It sees the same tools and schemas, but everytools/callrequest is evaluated against the policy before reaching the upstream server.
What happens when an MCP agent hits a spending limit?
The agent receives a denial message explaining why the call was blocked, e.g.[POLICYLAYER POLICY DENIED] Daily spending cap of $10,000.00 reached. The agent can then adapt — inform the user, try a smaller amount, or wait until the time window resets. The upstream MCP server never receives the blocked request.
Non-custodial spending controls for AI agent crypto wallets — enforce daily limits, per-tx caps, and recipient whitelists.
Pre-signature security suite for AI agents: flags wallet drains, permit-phishing, and risky actions before signing across EVM chains. Non-custodial.
Give AI agents spending power without giving them your wallet keys. Cloaked creates on-chain spending accounts with enforced constraints that agents cannot bypass - even if jailbroken or compromised.
AI-powered Solana token rug pull detection with ML ensemble scoring, honeypot detection, and temporal rug stage prediction.
Fight AI with AI. 6 adversarial AI agents debate crypto token risk before your agent trades.
Zen7 Payment Agent is the first implementation project of DePA (Decentralized Payment Agent), pioneers next-generation intelligent payment infrastructure.
MCP server for Aave — lending pool data, reserve info, user positions, and liquidation thresholds.
x402 payment gateway for AI agents — 12 crypto data tools (price, whale activity, gas, TVL, Fear & Greed, Dune queries) paid per-call in USDC on Stellar or Base. No API keys, no subscriptions.
Blockchain data across 100+ chains: token prices, NFTs, transfers, simulation, traces, Solana DAS
Trust infrastructure for AI agent commerce. Ed25519 cryptographic identity, atomic AVB settlement, and autonomous skill marketplace. 20 tools + 6 resources. OpenClaw SKILL.md compatible.
Sign in to leave a review
Use Google, GitHub, or an email account so ratings stay tied to real people.
No reviews posted yet.




