MPP (Machine Payments Protocol) Development Guide
MPP is an open, IETF-draft-style HTTP payment standard, co-authored by Stripe and Tempo,
that layers a Payment HTTP Authentication Scheme on top of 402 Payment Required so any HTTP
client — especially an AI agent — can pay for a resource as part of the request itself, with no
pre-provisioned account, API key, or billing relationship. Spec source: github.com/tempoxyz/mpp-specs
(published at paymentauth.org). Docs/directory: mpp.dev. TypeScript SDK: mppx on npm (SDKs
also exist for Python, Rust, Go, Ruby).
Quick Orientation
| Role |
Description |
| Client / Payer |
Sends a request, receives a Challenge (402), fulfills it (signs a tx, mints a token), retries with a Credential |
| Server / Payee |
Returns a Challenge describing accepted methods, verifies the Credential, settles, returns the resource + a Receipt |
| Relay (optional) |
Third-party infra exposing only validate/broadcast — MPP defines no relay API shape; Tempo's first-party relay is api.tempo.xyz |
Payment Flow:
- Client requests resource → server returns
402 with WWW-Authenticate: Payment ... (the Challenge)
- Client fulfills the Challenge (signs, pays, mints) → retries with
Authorization: Payment ... (the Credential)
- Server verifies + settles → returns
200 + Payment-Receipt header (the Receipt)
Always capitalize Challenge, Credential, Receipt as protocol proper nouns in docs/UI copy.
Package Reference
npm i mppx # TypeScript SDK — client, server, framework middlewares, CLI (package is `mppx`, NOT `mpp` or `@wevm/mppx`)
npm i -g mppx # also installs the `mppx` CLI (account/session management, curl-like paid requests)
mppx/client — payment-aware fetch wrapper, hooks, CLI internals
mppx/server — Mppx.create({ secretKey, methods }), .charge(), .compose()
mppx/express | mppx/hono | mppx/nextjs | mppx/elysia — framework middlewares + discovery()
mppx/tempo | mppx/evm | mppx/stripe | mppx/x402 — per-method exports
Other official SDKs: tempoxyz/pympp (Python), tempoxyz/mpp-rs (Rust). viem is a required peer
dependency for TypeScript client/server code.
Implementation Paths
Choose your path and read the reference file — don't guess wire formats or signing schemes from
memory, since this is a fast-moving, brand-new protocol; the reference files below are grounded in
the actual spec/SDK source, not general payment-protocol intuition.
| Task |
Reference File |
| Understand the wire protocol (Challenge/Credential/Receipt formats, status codes, error codes, versioning) |
[references/protocol-core.md] |
| Choose/implement a payment method (Tempo, EVM, Stripe, Lightning, ...) and its signing scheme |
[references/methods.md] |
| Build a client that pays for resources |
[references/client.md] |
Build a server that charges for resources, incl. multi-method compose() |
[references/server.md] |
| Monetize an MCP server tool, or wire up service discovery |
[references/mcp-and-discovery.md] |
| Security review an integration, or write tests for one |
[references/security-testing.md] |
| Understand or implement MPP ↔ x402 interoperability |
[references/x402-interop.md] |
Minimal Working Examples
Client — TypeScript
import { Mppx, tempo } from 'mppx/client'
import { privateKeyToAccount } from 'viem/accounts'
const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`)
const mppx = Mppx.create({ methods: [tempo({ account })] })
const res = await mppx.fetch('/api/photo') // transparently handles 402 → sign → retry
const data = await res.json()
Server — Express
import express from 'express'
import { Mppx, tempo } from 'mppx/express'
const app = express()
const mppx = Mppx.create({ methods: [tempo()] }) // reads MPP_SECRET_KEY from env
app.get('/premium', mppx.charge({ amount: '1' }), (req, res) => {
res.json({ data: 'paid content' })
})
MCP Server — Payment-Gated Tool
import { Mppx, tempo } from 'mppx/server'
import { Transport } from 'mppx'
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
const mppx = Mppx.create({
secretKey: process.env.MPP_SECRET_KEY!,
methods: [tempo.charge({ /* ... */ })],
transport: Transport.mcpSdk(),
})
const server = new McpServer({ name: 'paid-tools', version: '1.0.0' })
server.tool('get_premium_data', schema, async (params, extra) => {
const result = await mppx.charge({ amount: '0.01' })(extra)
if (result.status === 402) throw result.challenge // -> McpError code -32042
return result.withReceipt({ content: [{ type: 'text', text: 'premium data' }] })
})
Status Code Cheat Sheet
| Meaning |
Status |
| Payment needed (no/expired/invalid credential) |
402 — always, including retries after a failed proof |
| Non-payment auth failure |
401 |
| Payment succeeded, still not authorized by policy |
403 — never 402 |
| Payment succeeded |
200 + Payment-Receipt header |
Cache-Control: no-store is mandatory on every 402; Cache-Control: private is mandatory
whenever a Payment-Receipt is present. Full error-code table in [references/protocol-core.md].
Common Issues & Solutions
| Issue |
Solution |
Missing secret key at server startup |
Set MPP_SECRET_KEY (≥32 bytes) or pass secretKey to Mppx.create() |
| Payment retried endlessly / server keeps issuing fresh Challenges |
Cap maxPaymentRetries on the client; check the server isn't rejecting valid Credentials |
| Credential accepted from a different Challenge (replay) |
Bug in method implementation — the signature must derive from challenge.id/challenge.realm; see the binding checklist in [references/security-testing.md] |
| Want to accept both crypto and cards on one route |
Use mppx.compose() with tempo.charge() + stripe.charge(), see [references/server.md] |
| Want one route to accept both native MPP and x402 clients |
Use evm.charge({ x402: { facilitator } }), see [references/x402-interop.md] |
| Stripe SPT creation failing / insecure |
SPT must be minted server-side with a Stripe secret key — never client-side |
Using Tempo chain id 98865 |
Deprecated — use mainnet 4217 or testnet (Moderato) 42431 |
| Terminology nitpicks in review (escrow, USDC vs USDC.e) |
Say "reserve"/"lock up" not "escrow"; say "USDC.e" for Tempo's bridged token, plain "USDC" elsewhere |
Environment Variables
# Server
MPP_SECRET_KEY=... # >=32 bytes; HMAC root for Challenge-id binding — treat like a JWT signing key
WALLET_ADDRESS=0x... # or Tempo account — recipient of settled funds
# Client
PRIVATE_KEY=0x... # EVM/Tempo signer (viem Account)
# Stripe method (server-side only)
STRIPE_SECRET_KEY=sk_...
STRIPE_NETWORK_ID=... # for Shared Payment Token issuance
Reference Files
- [references/protocol-core.md] — Wire protocol: Challenge/Credential/Receipt formats, HTTP status semantics, Challenge-id HMAC binding, error codes, versioning, intents (charge/session/subscription/zero-dollar)
- [references/methods.md] — Per-rail signing schemes: Tempo (TIP-20, gasless, sessions), EVM (Permit2/EIP-3009/tx/hash), Stripe (Shared Payment Tokens), plus pointers for Lightning/Solana/Hedera/Stellar
- [references/client.md] —
mppx/client patterns: fetch wrapper, hooks, dual-protocol clients, agent spend limits (access keys), CLI
- [references/server.md] —
mppx/server patterns: Express/Hono/Next.js/Elysia middlewares, compose(), discovery, refunds
- [references/mcp-and-discovery.md] — MCP transport binding (
_meta fields, -32042/-32043), OpenAPI discovery, MPPScan/MPP Services directory, agent-permissions.json
- [references/security-testing.md] —
MPP_SECRET_KEY handling, Challenge-binding review checklist, common protocol mistakes, test conventions and a concrete test checklist
- [references/x402-interop.md] — MPP vs. x402 positioning, dual-protocol
evm.charge() pattern, when to use which
1---2name: mpp-dev3description: Comprehensive development support for MPP (Machine Payments Protocol) — the open, IETF-draft-style HTTP payment standard co-authored by Stripe and Tempo, built on HTTP 402 with a Challenge / Credential / Receipt model. Covers protocol design, client (payer) integration, server (payee) middleware, MCP server monetization, service discovery, method selection (Tempo, EVM/x402-compatible, Stripe Shared Payment Tokens, Lightning, and other rails), security review, and testing. USE THIS SKILL whenever the user: - Asks about MPP, the Machine Payments Protocol, paymentauth.org, mpp.dev, or mpp-specs - Wants to add pay-per-request/agentic pricing to an API using the `mppx` package - Integrates MPP with Express, Hono, Next.js, Elysia, or a plain fetch/Request-Response server - Builds an AI agent or coding agent that pays for tools/APIs autonomously via MPP - Implements an MCP server with payment-gated tools using the MPP JSON-RPC/MCP transport - Needs to choose or implement a payment method (Tempo, EVM/Permit2/EIP-30094---56# MPP (Machine Payments Protocol) Development Guide78MPP is an **open, IETF-draft-style HTTP payment standard**, co-authored by **Stripe and Tempo**,9that layers a `Payment` HTTP Authentication Scheme on top of `402 Payment Required` so any HTTP10client — especially an AI agent — can pay for a resource as part of the request itself, with no11pre-provisioned account, API key, or billing relationship. Spec source: `github.com/tempoxyz/mpp-specs`12(published at `paymentauth.org`). Docs/directory: `mpp.dev`. TypeScript SDK: `mppx` on npm (SDKs13also exist for Python, Rust, Go, Ruby).1415## Quick Orientation1617| Role | Description |18|---|---|19| **Client / Payer** | Sends a request, receives a **Challenge** (402), fulfills it (signs a tx, mints a token), retries with a **Credential** |20| **Server / Payee** | Returns a **Challenge** describing accepted methods, verifies the **Credential**, settles, returns the resource + a **Receipt** |21| **Relay** (optional) | Third-party infra exposing only `validate`/`broadcast` — MPP defines no relay API shape; Tempo's first-party relay is `api.tempo.xyz` |2223**Payment Flow:**241. Client requests resource → server returns `402` with `WWW-Authenticate: Payment ...` (the Challenge)252. Client fulfills the Challenge (signs, pays, mints) → retries with `Authorization: Payment ...` (the Credential)263. Server verifies + settles → returns `200` + `Payment-Receipt` header (the Receipt)2728Always capitalize **Challenge**, **Credential**, **Receipt** as protocol proper nouns in docs/UI copy.2930---3132## Package Reference3334```bash35npm i mppx # TypeScript SDK — client, server, framework middlewares, CLI (package is `mppx`, NOT `mpp` or `@wevm/mppx`)36npm i -g mppx # also installs the `mppx` CLI (account/session management, curl-like paid requests)37```3839```40mppx/client — payment-aware fetch wrapper, hooks, CLI internals41mppx/server — Mppx.create({ secretKey, methods }), .charge(), .compose()42mppx/express | mppx/hono | mppx/nextjs | mppx/elysia — framework middlewares + discovery()43mppx/tempo | mppx/evm | mppx/stripe | mppx/x402 — per-method exports44```4546Other official SDKs: `tempoxyz/pympp` (Python), `tempoxyz/mpp-rs` (Rust). `viem` is a required peer47dependency for TypeScript client/server code.4849---5051## Implementation Paths5253Choose your path and read the reference file — don't guess wire formats or signing schemes from54memory, since this is a fast-moving, brand-new protocol; the reference files below are grounded in55the actual spec/SDK source, not general payment-protocol intuition.5657| Task | Reference File |58|---|---|59| Understand the wire protocol (Challenge/Credential/Receipt formats, status codes, error codes, versioning) | [references/protocol-core.md] |60| Choose/implement a **payment method** (Tempo, EVM, Stripe, Lightning, ...) and its signing scheme | [references/methods.md] |61| Build a **client** that pays for resources | [references/client.md] |62| Build a **server** that charges for resources, incl. multi-method `compose()` | [references/server.md] |63| Monetize an **MCP server** tool, or wire up service **discovery** | [references/mcp-and-discovery.md] |64| **Security review** an integration, or write tests for one | [references/security-testing.md] |65| Understand or implement **MPP ↔ x402 interoperability** | [references/x402-interop.md] |6667---6869## Minimal Working Examples7071### Client — TypeScript7273```ts74import { Mppx, tempo } from 'mppx/client'75import { privateKeyToAccount } from 'viem/accounts'7677const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`)78const mppx = Mppx.create({ methods: [tempo({ account })] })7980const res = await mppx.fetch('/api/photo') // transparently handles 402 → sign → retry81const data = await res.json()82```8384### Server — Express8586```ts87import express from 'express'88import { Mppx, tempo } from 'mppx/express'8990const app = express()91const mppx = Mppx.create({ methods: [tempo()] }) // reads MPP_SECRET_KEY from env9293app.get('/premium', mppx.charge({ amount: '1' }), (req, res) => {94 res.json({ data: 'paid content' })95})96```9798### MCP Server — Payment-Gated Tool99100```ts101import { Mppx, tempo } from 'mppx/server'102import { Transport } from 'mppx'103import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'104105const mppx = Mppx.create({106 secretKey: process.env.MPP_SECRET_KEY!,107 methods: [tempo.charge({ /* ... */ })],108 transport: Transport.mcpSdk(),109})110111const server = new McpServer({ name: 'paid-tools', version: '1.0.0' })112113server.tool('get_premium_data', schema, async (params, extra) => {114 const result = await mppx.charge({ amount: '0.01' })(extra)115 if (result.status === 402) throw result.challenge // -> McpError code -32042116 return result.withReceipt({ content: [{ type: 'text', text: 'premium data' }] })117})118```119120---121122## Status Code Cheat Sheet123124| Meaning | Status |125|---|---|126| Payment needed (no/expired/invalid credential) | **402** — always, including retries after a failed proof |127| Non-payment auth failure | **401** |128| Payment succeeded, still not authorized by policy | **403** — never 402 |129| Payment succeeded | **200** + `Payment-Receipt` header |130131`Cache-Control: no-store` is mandatory on every 402; `Cache-Control: private` is mandatory132whenever a `Payment-Receipt` is present. Full error-code table in [references/protocol-core.md].133134---135136## Common Issues & Solutions137138| Issue | Solution |139|---|---|140| `Missing secret key` at server startup | Set `MPP_SECRET_KEY` (≥32 bytes) or pass `secretKey` to `Mppx.create()` |141| Payment retried endlessly / server keeps issuing fresh Challenges | Cap `maxPaymentRetries` on the client; check the server isn't rejecting valid Credentials |142| Credential accepted from a different Challenge (replay) | Bug in method implementation — the signature must derive from `challenge.id`/`challenge.realm`; see the binding checklist in [references/security-testing.md] |143| Want to accept both crypto and cards on one route | Use `mppx.compose()` with `tempo.charge()` + `stripe.charge()`, see [references/server.md] |144| Want one route to accept both native MPP and x402 clients | Use `evm.charge({ x402: { facilitator } })`, see [references/x402-interop.md] |145| Stripe SPT creation failing / insecure | SPT must be minted **server-side** with a Stripe secret key — never client-side |146| Using Tempo chain id `98865` | Deprecated — use mainnet `4217` or testnet (Moderato) `42431` |147| Terminology nitpicks in review (escrow, USDC vs USDC.e) | Say "reserve"/"lock up" not "escrow"; say "USDC.e" for Tempo's bridged token, plain "USDC" elsewhere |148149---150151## Environment Variables152153```bash154# Server155MPP_SECRET_KEY=... # >=32 bytes; HMAC root for Challenge-id binding — treat like a JWT signing key156WALLET_ADDRESS=0x... # or Tempo account — recipient of settled funds157158# Client159PRIVATE_KEY=0x... # EVM/Tempo signer (viem Account)160161# Stripe method (server-side only)162STRIPE_SECRET_KEY=sk_...163STRIPE_NETWORK_ID=... # for Shared Payment Token issuance164```165166---167168## Reference Files169170- **[references/protocol-core.md]** — Wire protocol: Challenge/Credential/Receipt formats, HTTP status semantics, Challenge-id HMAC binding, error codes, versioning, intents (charge/session/subscription/zero-dollar)171- **[references/methods.md]** — Per-rail signing schemes: Tempo (TIP-20, gasless, sessions), EVM (Permit2/EIP-3009/tx/hash), Stripe (Shared Payment Tokens), plus pointers for Lightning/Solana/Hedera/Stellar172- **[references/client.md]** — `mppx/client` patterns: fetch wrapper, hooks, dual-protocol clients, agent spend limits (access keys), CLI173- **[references/server.md]** — `mppx/server` patterns: Express/Hono/Next.js/Elysia middlewares, `compose()`, discovery, refunds174- **[references/mcp-and-discovery.md]** — MCP transport binding (`_meta` fields, `-32042`/`-32043`), OpenAPI discovery, MPPScan/MPP Services directory, agent-permissions.json175- **[references/security-testing.md]** — `MPP_SECRET_KEY` handling, Challenge-binding review checklist, common protocol mistakes, test conventions and a concrete test checklist176- **[references/x402-interop.md]** — MPP vs. x402 positioning, dual-protocol `evm.charge()` pattern, when to use which