Sentinel
An MCP proxy that enforces declarative YAML spend policies for AI agents using Agentcard, providing attribution and anomaly detection.
README
Sentinel
A spend-policy and observability layer for Agentcard — declarative YAML policies, an enforcing MCP proxy, and an attribution/anomaly dashboard for AI agents that hold real (or sandbox) money.
The problem
Agentcard issues single-use virtual Visa cards to AI agents, and its own writing describes exactly how that should be governed:
"One card per task. Each task is a separate financial segment with its own card, its own budget, and its own blast radius." … "A task that should cost $12 gets a $15 card. Not a $100 card 'to be safe.'" — Financial Zero Trust for AI Agents
"Agent expense systems need to attribute spend to tasks, workflows, and agent identities." … "Set velocity thresholds based on your historical patterns and alert when they are exceeded." — AI Agent Expense Management
The product today ships numeric limits: a budget, a per-transaction cap, and a human-approval threshold. There is no merchant allowlist/denylist, no velocity limit, no time-window rule, no per-agent policy scoping, and no attribution or anomaly layer — teams are told to build those themselves. Sentinel is that missing layer, built the way the zero-trust post says it should work:
- Policy engine — per-agent YAML rules (merchant allow/deny with wildcards, card velocity,
allowed hours with timezones, per-card caps, daily budgets, approval thresholds) evaluated
before any card exists, returning
ALLOW/DENY/NEEDS_APPROVALwith a machine-readable reason chain of every rule checked, in order. - MCP proxy — a stdio MCP server that wraps Agentcard's MCP server. Every tool passes
through unchanged except
create_cardandbuy_checkout, which are policy-checked first. Every decision — including the denials Agentcard never sees — lands in a SQLite ledger with agent id, task id, and the full rule trace. - Observability — a webhook receiver joins Agentcard transaction events to the ledger, so every settled charge maps to agent → task → policy decision. One dashboard page: spend by agent and task, budget burn-down vs policy, the denied-spend log, and anomaly flags (daily-spend z-score, first-seen merchant, off-hours attempts).
60-second quickstart
git clone <this repo> sentinel && cd sentinel
./demo.sh # creates a venv, seeds two agents, replays a scripted day,
# and serves the dashboard at http://127.0.0.1:8787/
No Agentcard account or credentials needed — the demo runs against a built-in mock that
implements Agentcard's documented API shapes (see docs/NOTES.md). Then try the killer
feature, a dry run that touches nothing:
.venv/bin/sentinel simulate --policy policies/example.yaml \
--agent research-bot --amount 1800 --merchant OPENAI \
--at "2026-07-15T23:30 America/Los_Angeles"
✓ merchant-deny merchant 'OPENAI' matches no deny pattern
✓ merchant-allow merchant 'OPENAI' matches allow pattern 'OPENAI'
✗ hours local time 23:30 PDT is outside allowed window 06:00-22:00
...
Verdict: DENY
Exit codes: 0 ALLOW · 1 NEEDS_APPROVAL · 2 DENY. Add --json for machines,
--state '{"spent_today_cents": 9000}'-style files for what-ifs, or --db sentinel.db
to evaluate against real ledger history.
Architecture
flowchart LR
subgraph agent side
A[AI agent / MCP client]
end
subgraph Sentinel
P[MCP proxy<br/>sentinel proxy]
E[Policy engine<br/>policies/*.yaml]
L[(SQLite ledger<br/>decisions + transactions)]
O[Observe server<br/>webhooks + dashboard]
end
U[Agentcard MCP server<br/>mcp.agentcard.sh<br/>or built-in mock]
W[Agentcard webhooks<br/>transaction.*]
A -- "create_card / buy_checkout<br/>(+ agent_id, task_id)" --> P
A -- "all other tools" --> P
P -- "evaluate" --> E
P -- "ALLOW → forward<br/>(sentinel args stripped)" --> U
P -- "every decision,<br/>incl. denials" --> L
W -- "signed events" --> O
O -- "join to decisions" --> L
O -- "dashboard :8787" --> A
Running it for real
-
Point the proxy at Agentcard (use a sandbox
sk_test_-provisioned OAuth token; never start with live money):cp .env.example .env # fill in AGENTCARD_MCP_URL + AGENTCARD_TOKEN sentinel proxy --policy policies/example.yaml --db sentinel.dbRegister that command as the MCP server in your agent's config instead of Agentcard's — e.g. in
claude_desktop_config.json/.mcp.json:{ "mcpServers": { "agentcard": { "command": "/path/to/.venv/bin/sentinel", "args": ["proxy", "--policy", "policies/example.yaml", "--db", "sentinel.db"], "env": { "AGENTCARD_MCP_URL": "https://mcp.agentcard.sh/mcp", "AGENTCARD_TOKEN": "…", "SENTINEL_AGENT_ID": "research-bot" } } } }Leave
AGENTCARD_MCP_URL/AGENTCARD_TOKENunset and the proxy uses the clearly-labeled mock — useful for CI and local development. -
Run the observe server and register its URL as a webhook endpoint in the Agentcard dashboard (subscribe to
transaction.*):AGENTCARD_WEBHOOK_SECRET=whsec_… sentinel observe --policy policies/example.yaml --db sentinel.db
The proxy adds three optional arguments to the intercepted tools: agent_id and task_id
(attribution, threaded into the ledger and joined to settled transactions) and
merchant_hint on create_card — Agentcard's create_card has no merchant parameter
(cards are open-loop until first charge), so the hint is the only pre-spend merchant control;
it is checked against policy and stripped before forwarding. A NEEDS_APPROVAL verdict
returns a hold; a human retries the call with sentinel_approved: true to release it.
sentinel_approved can never override a DENY.
Policy reference
defaults: # applied to agents not listed (omit = unknown agents denied)
max_per_card_cents: 1000
agents:
research-bot:
max_per_card_cents: 2500 # hard cap per card / checkout
daily_budget_cents: 10000 # forwarded spend per local calendar day
velocity: { max_cards: 5, per: 1h } # trailing-window card-creation limit (s/m/h/d)
merchants:
allow: ["OPENAI", "ANTHROPIC", "AWS"] # if present, everything else is denied
deny: ["*GAMBLING*", "*CRYPTO*"] # deny always wins over allow
hours: { allow: "06:00-22:00", tz: "America/Los_Angeles" } # overnight windows OK
require_approval_over_cents: 1500 # NEEDS_APPROVAL above this
Rule precedence (first deny wins; the full chain is always reported):
merchant-deny → merchant-allow → hours → velocity → max-per-card → daily-budget → approval-threshold. Merchant patterns are case-insensitive; */? are shell-style
wildcards against the whole descriptor, and plain patterns match the descriptor prefix or
any token prefix (OPENAI matches OPENAI *CHATGPT SUBSCR and PAYPAL *OPENAI).
Tests
.venv/bin/pytest --cov=sentinel.policy # engine is 96%+ covered
143 tests cover rule precedence, wildcard matching, timezone/DST/overnight-window edges, velocity window boundaries, ledger joins and dedupe, webhook signatures against known HMAC vectors, and an end-to-end run of the proxy over a real in-memory MCP session.
What this doesn't do (honest edition)
- It is not a network-level control. Sentinel gates what flows through its proxy. An agent holding raw Agentcard credentials — or a card number already minted — can spend without Sentinel ever knowing. Pair it with Agentcard's own budget/limit settings as the backstop; Sentinel is the fine-grained layer, not the outer wall.
- Merchant rules on
create_cardare advisory-by-construction. Agentcard's API takes no merchant at card-creation time, somerchant_hintis only as truthful as the agent supplying it. The hint is still valuable (it's checked, logged, and auditable against the settled descriptor later), andbuy_checkoutmerchant checks are real. Post-hoc, webhook descriptors expose any mismatch on the dashboard. - Merchant matching is string matching.
AWSwill not match a descriptor that readsAMAZON WEB SERVICES; card-network descriptors are messy. Write patterns against descriptors you've actually seen (the dashboard shows raw ones). - Approvals are honor-system at the MCP boundary.
sentinel_approved: trueis meant to be attached by a human-in-the-loop client, not the agent itself. If your agent framework can't guarantee that, treat NEEDS_APPROVAL as DENY. - Anomaly detection is deliberately simple (z-score with ≥3 days history, first-seen merchant, off-hours). A constant-spend history (σ=0) yields no z-flag; day one yields no flags at all. It's a tripwire, not a fraud model.
- The live-API path is untested against production. Card/transaction operations are
MCP-only (no public REST spec), and exercising the real server requires interactive OAuth
onboarding — so CI runs against
MockAgentcardClient, which mirrors the documented shapes indocs/NOTES.md. The policy engine, ledger, CLI, webhook receiver, and dashboard are fully real regardless of upstream. - Single-process ledger. SQLite (WAL) is plenty for one proxy + one observe server; it is not a multi-region audit store.
License
MIT — see LICENSE.
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.