Skip to main content
Glama
bhavyam2

Sentinel

by bhavyam2

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_APPROVAL with 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_card and buy_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

  1. 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.db

    Register 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_TOKEN unset and the proxy uses the clearly-labeled mock — useful for CI and local development.

  2. 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_card are advisory-by-construction. Agentcard's API takes no merchant at card-creation time, so merchant_hint is only as truthful as the agent supplying it. The hint is still valuable (it's checked, logged, and auditable against the settled descriptor later), and buy_checkout merchant checks are real. Post-hoc, webhook descriptors expose any mismatch on the dashboard.

  • Merchant matching is string matching. AWS will not match a descriptor that reads AMAZON 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: true is 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 in docs/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.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/bhavyam2/sentinel'

If you have feedback or need assistance with the MCP directory API, please join our Discord server