-
-
Leafline store: six agent tools, a budget form for the buyer (PayPal sandbox)
-
Buyer delegates a spending mandate: budget, per-order cap, categories, expiry
-
Agent journal: every tool call tied to the PayPal order it produced
-
Merchant analytics in AG Studio: agent vs human orders, what mandates refused
-
DisputeDesk: agent receipt as evidence for a PayPal dispute, refund via Toolkit
Inspiration
What it does
How we built it
Challenges we ran into
Accomplishments that we're proud of
What we learned
What's next for Leafline: an agent-ready storefront on PayPal
Inspiration
Shopping agents today mostly drive web pages the way a person does: scroll, click, parse, retry. PayPal's own WebMCP post (16.09.2026) puts the difference at "3 structured tool calls vs more than 30 interface-interaction steps". Two things are still missing for a small merchant who wants that traffic: a way to let an agent spend only what the buyer allowed, and a way to see afterwards what the agent did - especially when the buyer later says "that is not what I ordered".
What it does
For the buyer and their agent:
- The store publishes six tools -
search_products,get_budget,create_order,get_order_status,request_refund,open_dispute- once, and serves them two ways: WebMCP in the page (document.modelContext, native in Chrome with the WebMCP flag) and MCP for agents outside the browser (Streamable HTTP atPOST /mcp, plus stdio). - The buyer delegates a spending mandate: total budget, per-order cap, allowed categories, expiry, revoke at any time. The server checks it on every agent order before a PayPal order exists and refuses with a reason (
OVER_BUDGET,OVER_PER_ORDER,CATEGORY_NOT_ALLOWED,EXPIRED,REVOKED). - The agent can create an order but can never pay: the buyer approves it in PayPal (JS SDK v6 button or the approve link), and only the merchant server captures.
For the merchant:
- Every agent call is journaled per session and tied to the PayPal order it produced (the agent receipt). In our live runs an agent order took 4-5 tool calls end to end.
- Post-purchase claims go to DisputeDesk, which drafts the merchant's assessment and evidence from that receipt ("what the agent was shown, what the buyer delegated, who approved the payment"). The merchant refunds or accepts a PayPal dispute from the panel; these actions go through the PayPal Agent Toolkit.
- An AG Studio dashboard answers "how much of my revenue comes from agents, what did mandates stop, which claims should I fight" with cross-filtering, and the merchant can switch it to Studio's drag-and-drop editor.
- The dashboard is agent-readable too: AG Studio's read-only AI tools are published over WebMCP, so an agent in the merchant's browser can ask "allowed vs refused agent calls by tool" and get rows back.
How we built it
- Node 24 + TypeScript (no framework), PayPal Orders v2 (create with
custom_id/item SKUs carrying the agent session and mandate; idempotent capture with PayPal-Request-Id), JS SDK v6 one-time payment session. - PayPal Agent Toolkit 1.11.0 on the merchant side:
get_order,list_disputes,get_dispute,accept_dispute_claim,create_refund,get_refund. We put guards around it after reading its source: our own allow-list beforerun(), our refreshed OAuth token, a per-refund PayPal-Request-Id, error strings turned into exceptions. - MCP: official
@modelcontextprotocol/sdkserver (stateless Streamable HTTP), mandate viaX-Agent-MandateorBearer md_...; tool definitions shared with the WebMCP page (one source). - In-page demo agent: Gemini 3 Flash plans tool calls over the page's own WebMCP tools (fallback chain of several free-tier keys, then Mistral, then a deterministic rule planner - the demo never depends on one LLM).
- AG Studio 3.0 (+ its AI module) for analytics; AG Grid Community for the operational tables.
- Hosting: Render (free web service), blueprint in
render.yaml.
Challenges we ran into
- The Toolkit's
create_ordercannot carrycustom_id/SKUs and itspay_ordersends no PayPal-Request-Id, so order creation and capture stay on our own Orders v2 client; the Toolkit covers status, disputes, refunds. - The Toolkit executes any tool name passed to
run()regardless of the configured actions and caches its token forever - both handled on our side. - PayPal sandbox popups occasionally fail to open or show "Things don't appear to be working"; the browser E2E detects and reports these instead of hiding them. BY-country sandbox accounts cannot complete payments, so the demo uses US sandbox accounts.
- WebMCP is still behind a Chrome flag and its spec moves (navigator -> document.modelContext); we detect both.
Accomplishments we're proud of
- One tool definition serving three surfaces (WebMCP, MCP over HTTP, MCP over stdio) with the same server-side mandate checks and the same journal.
- A full live run on the public deployment: human checkout, agent checkout inside a mandate, refusal over budget, claim -> DisputeDesk case with agent evidence -> refund through the Toolkit, external MCP agent order, dashboard query by an agent - 18/18 steps, in a real browser against the PayPal sandbox.
- 84 automated tests; four independent code reviews by Google Jules plus a second reviewer from another vendor (Kimi K2.6), Semgrep and gitleaks; every accepted finding fixed, rejected ones explained in the README.
What we learned
The limit that really protects the buyer is the PayPal approval, not any header an agent sends; mandates are there to keep well-behaved agents inside what the buyer meant. Writing the threat model first made that explicit (see below).
What's next
- Pilot with one or two small merchants (we have none yet: this is a prototype, not a product).
- Signed mandates (buyer-side key) instead of bearer mandate ids.
- PayPal's own WebMCP integration once it opens beyond Store Sync merchants.
Threat model (short)
- An agent is identified only by its own headers; an agent that sends none looks like a human visitor and gets the human checkout. The store cannot prove who is calling.
- Every charge is approved by the buyer in PayPal; agents cannot approve or capture.
- A mandate id is a bearer capability (64 random bits): whoever holds it can create orders within its limits, each still needing buyer approval. Revocable at any time.
- Prices come only from the server catalog; agents send SKU + quantity.
- Merchant actions need
X-Merchant-Keyand refuse agent headers. On the public demo the merchant views are readable without the key (sandbox data, masked emails) so judges can see them; actions still need the key. - Rate limits use Cloudflare's client IP, with server-wide ceilings on LLM steps and writes.
- Sandbox only, enforced in code. Not audited.
Built With
- ag-grid
- ag-studio
- gemini
- mistral
- model-context-protocol
- node.js
- paypal-agent-toolkit
- paypal-js-sdk-v6
- paypal-orders-v2
- playwright
- render
- typescript
- webmcp
Log in or sign up for Devpost to join the conversation.