Agent-ready checkout for any PayPal merchant
Shopping agents are starting to buy on people's behalf through the open Universal Commerce Protocol. MCPal gives a PayPal merchant the UCP checkout endpoints those agents speak, and settles every order on PayPal with MCPal's own verbs: charge, or hold then capture_hold when the order ships, void_hold to release it, and refund. The merchant's policy limits, retry-safe PayPal request ids and the hash-chained decision log apply to agent orders exactly as they do to MCP tool calls. The same checkout also answers the Agentic Commerce Protocol (ACP) checkout_sessions API.
How an agent order flows
- Discover. The buyer agent reads
/.well-known/ucp: REST endpoint, capabilities (checkout, fulfillment, discount, order) and payment handlers. - Build the cart.
POST /checkout-sessionsandPUTupdates. The agent sends item ids, an address and discount codes; the merchant prices everything (integer cents) and answersready_for_completewhen nothing is missing. - Pay.
POST /checkout-sessions/{id}/completewith a payment instrument. The merchant calls MCPalcharge(orholdwith"dev.mcpal.settlement": "authorize"). The MCPal orderRef is derived from the checkout id, so a retried complete can never charge twice. - Policy. Totals at or above $250.00 stop at the merchant's approval rule: the checkout answers
requires_escalationwith acontinue_url, and nothing is charged. - After the sale.
GET /orders/{id}shows the order with PayPal's ids. The merchant ships (capture_hold), releases (void_hold) or refunds (refund) through/orders/{id}/ship|release|refund.
A buyer agent buys coffee
The demo merchant is Coffee Club Supply. A gpt-4.1-mini agent (temperature 0) is told: “Buy two bags of coffee for under $40 in total, shipped. Ship to Sam Sandbox, 2211 N First St, San Jose, CA 95131, US (email sam.sandbox@personal.example.com). Pay with my saved PayPal sandbox payment instrument: handler_id mcpal_paypal_sandbox, type card, credential { type: token, token: sandbox-wallet-token }. Use a discount code if the merchant advertises one. Pick the cheapest shipping. Do not complete the purchase if the total is $40.00 or more.” Its only tools are the UCP operations above; it never sees PayPal or MCPal.
Recorded run, Thu, 12 Nov 2026 15:39:27 GMT (4 tool calls). Full transcript: docs/agentic-commerce-proof.md.
| Status | completed |
|---|---|
| Total | $31.19 |
| UCP checkout / order | chk_ee71256eea53f3f20d / ord_ee29f215eedac0cc99 |
| MCPal orderRef | ucp-ee71256eea53f3f20d |
| PayPal order id | 5H043375UE8541721 |
| PayPal capture ids | 1EW29591SW950060R |
- user
Buy two bags of coffee for under $40 in total, shipped. Ship to Sam Sandbox, 2211 N First St, San Jose, CA 95131, US (email sam.sandbox@personal.example.com). Pay with my saved PayPal sandbox payment instrument: handler_id mcpal_paypal_sandbox, type card, credential { type: token, token: sandbox-wallet-token }. Use a discount code if the merchant advertises one. Pick the cheapest shipping. Do not complete the purchase if the total is $40.00 or more.
- agent → discover_merchant
{} - merchant ← discover_merchant
{"endpoint":"https://mcpal.vercel.app/ucp/v1","payment_handlers":{"com.paypal.sandbox.mcpal":[{"id":"mcpal_paypal_sandbox","version":"2026-04-08","config":{"environment":"sandbox","settles_with":"MCPal charge / hold on PayPal Orders v2","note":"Any credential.token (or a card credential) is settled by MCPal on the PayPal sandbox with PayPal's published sandbox test card. The token 'fail_token' simulates a decline."},"available_instruments":[{"type":"card"}]}],"dev.ucp.mock_payment_handler":[{"id":"mock_payment_handler","version":"2026-04-08","config":{"note":"The UCP conformance suite's handler id. Same settlement path as mcpal_paypal_sandbox; 'fail_token' declines before PayPal is called."} … - agent → create_checkout
{"line_items":[{"item_id":"bag_house_blend_12oz","quantity":2}],"buyer":{"first_name":"Sam","last_name":"Sandbox","email":"sam.sandbox@personal.example.com"},"shipping_address":{"street_address":"2211 N First St","address_locality":"San Jose","address_region":"CA","postal_code":"95131","address_country":"US"},"discount_codes":["AGENT10"]} - merchant ← create_checkout
{"id":"chk_ee71256eea53f3f20d","status":"incomplete","line_items":[{"id":"li_1","item_id":"bag_house_blend_12oz","title":"House Blend whole bean, 12 oz bag","unit_price_cents":1400,"quantity":2}],"shipping":{"method_id":"shipping_1","destinations":[{"type":"shipping_address","id":"dest_1","street_address":"2211 N First St","address_locality":"San Jose","address_region":"CA","postal_code":"95131","address_country":"US"}],"selected_destination_id":"dest_1","selected_option_id":null,"options":[{"id":"std-ground","title":"Standard ground (5-7 business days)","cents":599},{"id":"express-2day","title":"Express (2 business days)","cents":1499}]},"discounts":{"codes":["AGENT10"],"applied":[{"code":" … - agent → update_checkout
{"checkout_id":"chk_ee71256eea53f3f20d","selected_shipping_option_id":"std-ground"} - merchant ← update_checkout
{"id":"chk_ee71256eea53f3f20d","status":"ready_for_complete","line_items":[{"id":"li_1","item_id":"bag_house_blend_12oz","title":"House Blend whole bean, 12 oz bag","unit_price_cents":1400,"quantity":2}],"shipping":{"method_id":"shipping_1","destinations":[{"type":"shipping_address","id":"dest_1","street_address":"2211 N First St","address_locality":"San Jose","address_region":"CA","postal_code":"95131","address_country":"US"}],"selected_destination_id":"dest_1","selected_option_id":"std-ground","options":[{"id":"std-ground","title":"Standard ground (5-7 business days)","cents":599},{"id":"express-2day","title":"Express (2 business days)","cents":1499}]},"discounts":{"codes":["AGENT10"],"app … - assistant
The total for two bags of House Blend whole bean coffee with the 10% agent discount and standard ground shipping is $31.19, which is under your $40 limit. I will now complete the purchase using your saved PayPal sandbox payment instrument.
- agent → complete_checkout
{"checkout_id":"chk_ee71256eea53f3f20d","handler_id":"mcpal_paypal_sandbox","token":"sandbox-wallet-token"} - merchant ← complete_checkout
{"id":"chk_ee71256eea53f3f20d","status":"completed","line_items":[{"id":"li_1","item_id":"bag_house_blend_12oz","title":"House Blend whole bean, 12 oz bag","unit_price_cents":1400,"quantity":2}],"shipping":{"method_id":"shipping_1","destinations":[{"type":"shipping_address","id":"dest_1","street_address":"2211 N First St","address_locality":"San Jose","address_region":"CA","postal_code":"95131","address_country":"US"}],"selected_destination_id":"dest_1","selected_option_id":"std-ground","options":[{"id":"std-ground","title":"Standard ground (5-7 business days)","cents":599},{"id":"express-2day","title":"Express (2 business days)","cents":1499}]},"discounts":{"codes":["AGENT10"],"applied":[{" … - assistant
The order for two bags of House Blend whole bean coffee with standard ground shipping and a 10% discount has been completed successfully. The total was $31.19, under your $40 limit. Your order ID is ord_ee29f215eedac0cc99.
Official UCP conformance suite: 55 passed, 14 failed, 9 skipped (78 tests)
Run on 2026-11-12 against this site with the suite's own runner (conformance @ 1201fb7, ucp-sdk 0.4.6). Every one of the 14 failures is in a feature this demo merchant does not implement and lists below as unsupported: order webhooks to the platform (7), platform-written order updates via PUT /orders (4) and the shipping-simulation endpoint (3). Checkout lifecycle, idempotency, totals, discounts, validation, fulfillment structure and card credentials all pass. Raw output and fixtures: docs/ucp-official.
Spec-derived checks
UCP spec-derived checks (our implementation of the UCP conformance modules), not the official suite run. Checks re-implemented in test/ucp-conformance.test.ts from the source of github.com/Universal-Commerce-Protocol/conformance at commit 1201fb78e85217c5f2df060066ccbb0a1d1dce0b and the UCP schemas at commit 98607bc53f6f5e43ec15b9f96b82cba4912f0fe4. The official Python suite was not executed: running its code (and installing ucp-sdk 0.4.6) was blocked by this environment's permission policy. To run it yourself: clone the conformance repo, then SERVER_URL=https://mcpal.vercel.app uv run pytest with a conformance_input.json naming bag_house_blend_12oz / bag_panama_geisha_4oz and test_fixtures.json with AGENT10 (10%), BEANS20 (20%), FIVEOFF (5.00). local: 54/54 passed · production: 54/54 passed. Unsupported modules are listed as unsupported, not counted as passes. Details: docs/ucp-conformance.md.
| Official module | Coverage | local | production | Note |
|---|---|---|---|---|
| shopping/checkout/protocol_test.py | covered | 3/3 | 3/3 | |
| shopping/checkout/lifecycle_test.py | covered | 11/11 | 11/11 | |
| shopping/checkout/idempotency_test.py | covered | 4/4 | 4/4 | |
| shopping/checkout/totals_test.py | covered | 6/6 | 6/6 | |
| shopping/checkout/business_logic_test.py | partial | 7/7 | 7/7 | test_buyer_consent not re-implemented: the buyer_consent capability is not advertised (consent is stored and echoed, not acted on). |
| shopping/discount/semantics_test.py | covered | 6/6 | 6/6 | |
| shopping/fulfillment/fulfillment_test.py | partial | 3/3 | 3/3 | Known-customer stored-address tests (5) are unsupported: the merchant keeps no customer address book. Item-based free shipping is not offered (the official test would skip). |
| shopping/fulfillment/structure_test.py | covered | 2/2 | 2/2 | |
| shopping/order/order_test.py | partial | 1/1 | 1/1 | GET /orders/{id} and expectations are checked. PUT /orders/{id} (platform-written fulfillment events and adjustments) is unsupported; shipping and refunds go through the merchant's own MCPal actions instead. |
| shopping/validation/validation_test.py | covered | 5/5 | 5/5 | |
| shopping/validation/invalid_input_test.py | unsupported | - | - | Needs PUT /orders/{id}, which is not implemented. |
| common/payments/card_credential_test.py | covered | 1/1 | 1/1 | The card number is never forwarded: the handler charges PayPal's published test card through MCPal. |
| common/payments/token_binding_test.py | not checked | - | - | A token with a binding completes (binding accepted but not verified). |
| common/payments/ap2_test.py | unsupported | - | - | AP2 mandates are ignored, not verified. |
| shopping/order/webhook_test.py | unsupported | - | - | No order webhooks are delivered to the platform. |
| shopping/order/webhook_structure_test.py | unsupported | - | - | No order webhooks (so no Standard Webhooks headers, retries or RFC 9421 signatures). |
| shopping/order/simulation_url_security_test.py | unsupported | - | - | No /testing/simulate-shipping endpoint; shipping is the merchant's POST /orders/{id}/ship (MCPal capture_hold). |
| MCPal extension (not UCP): ship-later settlement | extra | 1/1 | 1/1 | Authorize at complete (MCPal hold), capture on ship (capture_hold), partial refund (refund). |
| ACP checkout_sessions (not UCP) | extra | 4/4 | 4/4 | Spec-derived from the ACP 2026-04-17 OpenAPI; there is no ACP conformance suite to mirror. |
Endpoints
| GET | https://mcpal.app/.well-known/ucp | UCP business profile (discovery) |
| POST | https://mcpal.app/ucp/v1/checkout-sessions | create checkout |
| GET / PUT | https://mcpal.app/ucp/v1/checkout-sessions/{id} | get / update (full replacement) |
| POST | https://mcpal.app/ucp/v1/checkout-sessions/{id}/complete | pay: MCPal charge, or hold |
| POST | https://mcpal.app/ucp/v1/checkout-sessions/{id}/cancel | cancel (void_hold if anything was held) |
| GET | https://mcpal.app/ucp/v1/orders/{id} | order, with PayPal ids under dev.mcpal.settlement |
| POST | https://mcpal.app/ucp/v1/orders/{id}/ship | release | refund | merchant actions (MCPal extension): capture_hold, void_hold, refund |
| POST | https://mcpal.app/acp/checkout_sessions | ACP create (same engine; Authorization: Bearer <any>, API-Version: 2026-04-17) |
| GET / POST | https://mcpal.app/acp/checkout_sessions/{id} | ACP get / update |
| POST | https://mcpal.app/acp/checkout_sessions/{id}/complete | cancel | ACP complete (payment_data) / cancel |
Headers: UCP-Agent (required; a version newer than 2026-04-08 gets 422), Idempotency-Key (same key + same body replays the first answer; a different body gets 409), Request-Id. Payment handlers: mcpal_paypal_sandbox and the conformance suite's mock_payment_handler; the token fail_token simulates a decline.
curl -s https://mcpal.app/.well-known/ucp
curl -s -X POST https://mcpal.app/ucp/v1/checkout-sessions \
-H 'UCP-Agent: profile="https://agent.example/profile"' \
-H "Idempotency-Key: $(uuidgen)" -H 'content-type: application/json' \
-d '{"line_items":[{"item":{"id":"bag_house_blend_12oz"},"quantity":2}],
"discounts":{"codes":["AGENT10"]},
"fulfillment":{"methods":[{"type":"shipping","destinations":[{"id":"d1",
"street_address":"2211 N First St","address_locality":"San Jose",
"address_region":"CA","postal_code":"95131","address_country":"US"}],
"groups":[{"selected_option_id":"std-ground"}]}]}}'
# then, with the returned id:
curl -s -X POST https://mcpal.app/ucp/v1/checkout-sessions/<id>/complete \
-H 'UCP-Agent: profile="https://agent.example/profile"' \
-H "Idempotency-Key: $(uuidgen)" -H 'content-type: application/json' \
-d '{"payment":{"instruments":[{"id":"i1","handler_id":"mcpal_paypal_sandbox",
"type":"card","credential":{"type":"token","token":"sandbox-wallet-token"}}]}}'The demo merchant
| Item id | Product | Price |
|---|---|---|
| bag_house_blend_12oz | House Blend whole bean, 12 oz bag | $14.00 |
| bag_ethiopia_yirgacheffe_12oz | Ethiopia Yirgacheffe whole bean, 12 oz bag | $18.00 |
| bag_decaf_colombia_12oz | Decaf Colombia ground, 12 oz bag | $16.00 |
| mug_ceramic_black | Coffee Club ceramic mug, 12 oz | $12.00 |
| filters_v60_100 | V60 paper filters, pack of 100 | $8.00 |
| bag_panama_geisha_4oz | Panama Geisha whole bean, 4 oz (sold out) | sold out |
Shipping: Standard ground (5-7 business days) $5.99 (free from $50.00) · Express (2 business days) $14.99 · International express (5-10 business days) $29.99. Discount codes: AGENT10, BEANS20, FIVEOFF (AGENT10 is the one the profile advertises). Hosted-demo cap: $500.00 per checkout.
Terms: this is a demo merchant. Nothing ships.
Privacy: buyer names, emails and addresses sent to the demo are stored with the checkout session in private storage and used only to render it.
Not supported (yet)
- Order webhooks to the platform, request/response signatures (RFC 9421) and the conformance suite's simulate-shipping hook.
- Platform-written order updates (
PUT /orders/{id}), stored customer address books, AP2 mandates, verified token binding. - Real card or wallet credentials: every accepted instrument settles with PayPal's published test card. Card numbers are never forwarded.
- MCP and A2A transports for UCP; the REST binding only.
- ACP: delegated payment (the PSP vault-token flow), order webhooks to the agent platform and signed requests. The ACP endpoints share the UCP engine, so the same handler settles them.
Every merchant decision is in the decision log (source “merchant checkout (UCP)”).