Skip to main content
Glama
SaiKrishnaMulukutla

Payments Agent Gateway (MCP)

Payments Agent Gateway (MCP)

Python MCP Pydantic Tests Last commit CI

An MCP server that safely exposes a correctness-critical, double-entry payments ledger to a probabilistic AI agent.

Thesis: How do you let an LLM agent operate a money-moving system without it double-charging, exceeding its authority, or being steered by prompt injection? The gateway answers that — the model generates intent; the gateway enforces the boundary; the ledger stays the source of truth.

Sits on top of my payments-wallet-service (Java/Spring Boot: immutable double-entry ledger, idempotency, saga, transactional outbox).


Why it's interesting

The backend already solves deterministic correctness. The new problem is a non-deterministic caller:

  • The model will retry. A lost response must not create a second payment → the gateway maps a logical operation_id → a stable backend idempotency key, so a retry replays instead of double-charging. (The centerpiece — connects distributed-systems work to agentic engineering.)

  • The model can be injected / hallucinate. Every mutating request passes capability policy (scopes + account allow-list + amount ceiling) before the backend is touched — the OWASP structured-invocation control: “the model proposes; the validator disposes.”

  • Some actions are irreversible. Amounts over the agent's autonomous limit return APPROVAL_REQUIRED (human-in-the-loop) — a model saying “I confirm” is not security.

Related MCP server: valta-mcp

Architecture

User ──NL──▶ LLM/Agent ──MCP(stdio/http)──▶  Payments MCP Gateway (Python/FastMCP)
                                             • tool schemas + validation
                                             • capability policy (AgentPrincipal + scopes)
                                             • operation_id → idempotency key
                                             • agent error taxonomy + audit
                                                     │ PaymentBackend (protocol)
                                       ┌─────────────┴─────────────┐
                                       ▼                           ▼
                              HttpPaymentBackend            DemoPaymentBackend
                              (real Spring Boot API)        (in-memory; dev + evals)
                                       │
                                       ▼   PostgreSQL · Redis · Kafka
                              double-entry · idempotency · saga · outbox · reconciliation

Rule: the gateway constrains what an agent may request; the ledger determines what is financially valid and executes it atomically. The gateway never re-implements balances, idempotency, or postings.

Safety model (maps to OWASP MCP controls)

Control

Here

Authenticated identity

OAuth 2.1 resource server: a verified bearer token resolves the per-request AgentPrincipal (identity.py) — the model never picks its own identity

Deterministic authorization

a signed payment mandate pins payer/payee/amount; mandate_id anchors idempotency, so agent arg-drift/hallucination is rejected, not executed (mandate.py)

Structured invocation

policy.py validates scope + account + amount before any backend call

Human-in-the-loop

APPROVAL_REQUIRED for amounts over the principal's ceiling

Context compartmentalization

least-privilege AgentPrincipal (scopes + account allow-list); creds never seen by the model

Model-readable failures

stable error taxonomy: {code, message, retryable, retry_after_seconds, suggested_action, correlation_id}suggested_action is gateway-owned (not echoed from backend data)

Auditability

per-call record correlating agent intent → backend execution

Tools

Tool

Scope

Annotation

get_payment / get_balance / get_account_ledger

payments:read

read-only (ledger bounded)

create_payment

payments:create

idempotent

refund_payment

payments:refund

destructive

check_ledger_integrity

payments:admin

read-only, admin

create_payment / refund_payment also accept an optional signed mandate that authorizes the exact transaction. Plus resources payments://capabilities, payments://payment/{id} and prompt explain_payment.

Quickstart

python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"

# run it under the MCP Inspector (interactive)
mcp dev src/payments_mcp/server.py

# or register with Claude Desktop
mcp install src/payments_mcp/server.py

# quality gates (also run in CI)
ruff check src evals tests && mypy && pytest -q

Defaults to PAYMENTS_BACKEND=demo (in-memory, no external services). Set PAYMENTS_BACKEND=http

  • PAYMENTS_BASE_URL to drive the real Spring Boot service.

Remote / authenticated (OAuth 2.1 resource server):

PAYMENTS_TRANSPORT=streamable-http PAYMENTS_HOST=0.0.0.0 \
  PAYMENTS_AUTH_SECRET=… PAYMENTS_AUTH_ISSUER=https://your-idp/ \
  PAYMENTS_AUTH_RESOURCE_URL=http://localhost:8000 \
  PAYMENTS_MANDATE_SECRET=… \
  python -m payments_mcp.server

Unauthenticated calls get 401 + a WWW-Authenticate pointer to /.well-known/oauth-protected-resource (RFC 9728). With PAYMENTS_MANDATE_SECRET set, human approvals close into signed mandates via the /approvals routes (GET list · POST create · POST /{id}/approve → mandate · POST /{id}/reject).

Demo (the money shot)

Ask the agent: “Pay 50 from acct-A to acct-B, then retry the exact same operation.” → one payment, one debit. Then “pay 500000 from acct-A to acct-B”APPROVAL_REQUIRED. Then “pay from acct-Z”ACCOUNT_NOT_ALLOWED. The audit log shows intent → idempotency key → payment → (in http mode) ledger postings.

Evals

python -m evals.runner drives a real LLM (Anthropic tool-use loop) through behavior scenarios — normal / risk / authorization / hallucination / retry / adversarial (prompt-injection & jailbreak) — against a fresh DemoPaymentBackend per scenario, and asserts machine-checkable outcomes: duplicate-financial-operation count = 0 and no unsafe money movement even when the model is instructed to override its limits. Needs ANTHROPIC_API_KEY in .env (pip install -e ".[evals]"); without it the suite skips and the deterministic exactly-once proof still runs via pytest tests/test_m3_create.py.

Layout

src/payments_mcp/  server.py · gateway.py · policy.py · operations.py · errors.py · audit.py · config.py · backend/{base,http_backend,demo_backend}.py
evals/             scenarios.py · runner.py
tests/             backend · policy · create (the centerpiece) · refund/admin
scripts/           smoke_m0..m5.py (end-to-end via a real MCP client)
docs/              PLAN · HLD · LLD

Non-goals

No second payments backend · no MCP-side financial logic · no autonomous reconciliation/repair · no generic SQL/call_api tool · no model-generated authorization · no fake approval · no vector-DB-for-RAG-decoration. The project stays a boundary between an agent and a correct ledger.

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    B
    maintenance
    An MCP server for Valta that exposes financial governance tools for AI agents, including spend authorization and audit trail via MCP-compatible clients.
    16
    31 npm
    1
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    MCP server that enables AI agents to propose USDC payments on the Soroban blockchain with deterministic policy enforcement and injection protection, while providing payment status and attestation tools.
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    MCP server that enables AI agents to request human approval before spending money, check approval status, verify signed tokens, and manage API keys.
    4 npm
    MIT