Agent Skills: MPP - Machine Payments Protocol

Build with MPP (Machine Payments Protocol) - the open protocol for machine-to-machine payments over HTTP 402. Use when developing paid APIs, payment-gated content, AI agent payment flows, MCP tool payments, pay-per-token streaming, or any service using HTTP 402 Payment Required. Covers the mppx TypeScript SDK with Hono/Express/Next.js/Elysia middleware, pympp Python SDK, and mpp Rust SDK. Supports Tempo stablecoins, Stripe cards, Lightning Bitcoin, and custom payment methods. Includes charge (one-time) and session (streaming pay-as-you-go) intents. Make sure to use this skill whenever the user mentions mpp, mppx, machine payments, HTTP 402 payments, Tempo payments, payment channels, pay-per-token, paid API endpoints, or payment-gated services.

UncategorizedID: tenequm/claude-plugins/mpp

Install this agent skill to your local

pnpm dlx add-skill https://github.com/tenequm/skills/tree/HEAD/skills/mpp

Skill Files

Browse the full folder contents for mpp.

Download Skill

Loading file tree…

skills/mpp/SKILL.md

Skill Metadata

Name
mpp
Description
Stripe crypto profile ID for on-chain deposits.

MPP - Machine Payments Protocol

MPP is an open protocol (co-authored by Tempo and Stripe) that standardizes HTTP 402 Payment Required for machine-to-machine payments. Clients pay in the same HTTP request - no accounts, API keys, or checkout flows needed.

The core protocol spec is submitted to the IETF as the Payment HTTP Authentication Scheme.

Code in this skill uses placeholder token names (<USDC_TEMPO_MAINNET>, <PATHUSD_TESTNET>); the real addresses live in the Tempo documentation and references/tempo-method.md.

Core Architecture

Three primitives power every MPP payment:

  1. Challenge - server-issued payment requirement (in WWW-Authenticate: Payment header)
  2. Credential - client-submitted payment proof (in Authorization: Payment header)
  3. Receipt - server confirmation of successful payment (in Payment-Receipt header)

Payment Methods & Intents

MPP is payment-method agnostic. Each method defines its own settlement rail:

| Method | Rail | SDK Package | Status | |--------|------|-------------|--------| | Tempo | TIP-20 stablecoins on Tempo chain | mppx (built-in) | Production | | Stripe | Cards/wallets (SPT) + on-chain crypto deposit | mppx (built-in) | Production | | EVM | EIP-3009 stablecoin authorizations (x402-exact compatible) | mppx (built-in) | Production | | Lightning | Bitcoin over Lightning Network | @buildonspark/lightning-mpp-sdk | Production | | Stellar | SEP-41 tokens on Stellar, charge + channel | @stellar/mpp | Production (channel wire spec still being drafted - subject to change) | | Solana | Solana-native charge + session (SOL, SPL, Token-2022) | @solana/mpp | Production | | Monad | Monad charge (ERC-3009, settlement modes) | @monad-crypto/mpp | Production | | NEAR Intents | Cross-chain charge via 1Click deposit addresses | @defuse-protocol/nearintents-mpp-sdk | Production (not trustless - routes through a settlement backend, advertised as methodDetails.settlementBackend: "near-intents" for per-method risk policy) | | RedotPay | RedotPay balance (rdt) or stablecoin proof, charge only | @redotpay/mpp | Production | | Card | Encrypted network tokens (Visa) | mpp-card | Production | | Custom | Any rail | Method.from() + Method.toClient/toServer | Extensible |

Per-method deep dives: references/tempo-method.md, references/stripe-method.md, references/lightning-method.md, references/custom-methods.md.

| Intent | Pattern | Best For | |--------|---------|----------| | charge | One-time payment per request | API calls, content access, fixed-price endpoints | | session | Pay-as-you-go over payment channels | LLM streaming, metered billing, high-frequency APIs | | subscription | Recurring access via an authorized key (Tempo) - see references/subscriptions.md | Plans/tiers where access is separated from per-request billing |

Quick Start: Server (TypeScript)

import { Mppx, tempo } from 'mppx/server'

const mppx = Mppx.create({
  methods: [tempo({
    currency: '<PATHUSD_TESTNET>', // pathUSD testnet
    recipient: '0xYourAddress',
  })],
})

export async function handler(request: Request) {
  const result = await mppx.charge({ amount: '0.01' })(request)
  if (result.status === 402) return result.challenge
  return result.withReceipt(Response.json({ data: '...' }))
}

Install: npm install mppx viem (mppx 0.8.15 requires viem >= 2.54.0).

Validate the finished server end-to-end with npx mppx validate http://localhost:3000.

Quick Start: Client (TypeScript)

import { privateKeyToAccount } from 'viem/accounts'
import { Mppx, tempo } from 'mppx/client'

// Polyfills globalThis.fetch to handle 402 automatically
Mppx.create({
  methods: [tempo({ account: privateKeyToAccount('0x...') })],
})

const res = await fetch('https://api.example.com/paid')
// Payment happens transparently when server returns 402

In browsers, mppx 0.6.0 changed the default: polyfilled fetch only sends Accept-Payment to same-origin endpoints, so cross-origin paid APIs need acceptPaymentPolicy ('always' / { origins: [...] }). Client fetch retries incremental challenges up to maxPaymentRetries (default 3). For non-global alternatives (Fetch.from/polyfill/restore, Mppx.restore()), see references/typescript-sdk.md.

Quick Start: Server (Python)

from fastapi import FastAPI
from mpp import Credential, Receipt
from mpp.server import Mpp
from mpp.methods.tempo import tempo, ChargeIntent

app = FastAPI()
server = Mpp.create(method=tempo(
    currency="<PATHUSD_TESTNET>",
    recipient="0xYourAddress", intents={"charge": ChargeIntent()},
))

@app.get("/resource")
@server.pay(amount="0.50")
async def get_resource(request, credential: Credential, receipt: Receipt):
    return {"data": "paid content", "payer": credential.source}

Install: pip install "pympp[tempo]". See references/python-sdk.md for full patterns.

Quick Start: Server (Rust)

Install: cargo add mpp --features tempo,server. See references/rust-sdk.md for full patterns.

Framework Middleware (TypeScript)

Each framework has its own import (mppx/nextjs, mppx/hono, mppx/express, mppx/elysia):

// Next.js
import { Mppx, tempo } from 'mppx/nextjs'
const mppx = Mppx.create({ methods: [tempo({ currency: '<PATHUSD_TESTNET>', recipient: '0x...' })] })
export const GET = mppx.charge({ amount: '0.1' })(() => Response.json({ data: '...' }))

// Hono
import { Mppx, tempo } from 'mppx/hono'
app.get('/resource', mppx.charge({ amount: '0.1' }), (c) => c.json({ data: '...' }))

See references/typescript-sdk.md for Express and Elysia examples.

Sessions: Pay-as-You-Go Streaming

Sessions open a payment channel once, then use off-chain vouchers for each request - no blockchain transaction per request. Sub-100ms latency, near-zero per-request fees.

Sessions v2 (default since mppx 0.7.0): tempo.session() is the TIP-1034 precompile channel flow; the earlier escrow-contract implementation is Sessions v1, still available as the deprecated tempo.sessionLegacy. A v2-expecting client rejects a v1 session and falls back to the charge path, so keep client and server on matching flows. Two client APIs: tempo.session({ account, maxDeposit }) registers the method with Mppx.create() (transparent 402 handling via fetch), while tempo.session.manager({ account, maxDeposit }) returns a managed client for direct lifecycle control (.sse(), .close()).

// Server - session endpoint with automatic settlement
const mppx = Mppx.create({
  methods: [tempo.session({
    currency: '<PATHUSD_TESTNET>', recipient: '0x...',
    store: Store.redis(redis),
    settlementSchedule: { amount: '1.00', intervalMs: 300_000 },
    bootstrap: true, // let returning clients recover their channel on this route
  })],
})
const result = await mppx.session({ amount: '0.001', unitType: 'token' })(request)
if (result.status === 402) return result.challenge
return result.withReceipt(Response.json({ data: '...' }))
// Server - SSE streaming with per-word billing
export const GET = mppx.session({ amount: '0.001', unitType: 'word' })(
  async () => async function* (stream) {
    for (const word of ['hello', 'world']) {
      await stream.charge()
      yield word
    }
  }
)

// Client - session with auto-managed channel
Mppx.create({ methods: [tempo({ account, maxDeposit: '1' })] })
const res = await fetch('http://localhost:3000/api/resource')
// 1st request: opens channel on-chain; 2nd+: off-chain vouchers

Sessions also stream over WebSocket via Ws.serve(). See references/sessions.md for the full lifecycle, settlement, stores, SSE and WebSocket patterns, and channel recovery.

Multi-Method Support

Accept Tempo stablecoins, Stripe cards, and Lightning Bitcoin on a single endpoint:

const mppx = Mppx.create({
  methods: [
    tempo({ currency: '<PATHUSD_TESTNET>', recipient: '0x...' }),
    stripe.charge({ client: new Stripe(key), networkId: 'profile_...', paymentMethodTypes: ['card'] }),
    spark.charge({ mnemonic: process.env.MNEMONIC! }),
  ],
})

Use Mppx.compose() to present multiple methods in a single 402 response with per-route pricing. Apply the same branch at the challenge site and the verification site, or the 402 advertises fewer options than the server accepts. See references/typescript-sdk.md.

Payment Links (HTML)

Setting html: true on a payment method config renders a browser-friendly payment page when a 402 endpoint is visited in a browser, with theming, multi-method compose tabs, and Solana wallet support. Service workers handle credential submission, then the page reloads with the paid response.

Customize via mppx/html exports (Config, Text, Theme), and build a custom method's payment link with Html.init(methodName).

Zero-Dollar Auth (Proof Credentials)

Authenticate agent identity without payment. Clients sign an EIP-712 proof over the challenge ID instead of creating a transaction - no gas burned, no funds transferred.

// Server - zero-dollar charge, with a store for replay protection
const mppx = Mppx.create({
  methods: [tempo.charge({ currency: '<PATHUSD_TESTNET>', recipient: '0x...', store })],
})
const result = await mppx.charge({ amount: '0' })(request)

Since mppx 0.8.0 these proofs are bound to the payer wallet: the EIP-712 Proof typed data (exposed as tempo.Proof) carries an account field at domain version 3, so a proof signed for one account no longer verifies against another.

Use cases: identity verification, long-running job polling, paid unlock with free subsequent access, multi-step agent pipelines. See mpp.dev/advanced/identity.

Payments Proxy

Gate existing APIs behind MPP payments:

// import { openai, Proxy } from 'mppx/proxy' - a service inside Proxy.create({ services: [...] })
openai({
  apiKey: process.env.OPENAI_API_KEY,
  routes: {
    'POST /v1/chat/completions': mppx.charge({ amount: '0.05' }),
    'GET /v1/models': true, // literal `true` marks a free route
  },
})

Built-in presets openai(), anthropic(), stripe(), plus custom() for any upstream. See references/discovery-and-proxy.md for Proxy.create(), the discovery endpoints it serves, and the discovery() helper for non-proxy servers.

MCP Transport

MCP tool calls can require payment using JSON-RPC error code -32042 (servers may also issue -32043):

// Server - import tempo from mppx/server, NOT mppx/tempo
import { McpServer } from 'mppx/mcp/server'
import { tempo } from 'mppx/server'
const server = McpServer.wrap(baseServer, { methods: [tempo.charge({ /* ... */ })], secretKey })

// Client - payment-aware MCP client (import tempo from mppx/client)
import { McpClient } from 'mppx/mcp/client'
import { tempo } from 'mppx/client'
const mcp = McpClient.wrap(client, { methods: [tempo({ account })] })
const result = await mcp.callTool({ name: 'premium_tool', arguments: {} })

MCP-over-HTTP challenges settle in the same payment-aware fetch, and transports are pluggable via Transport.from/http/mcp/mcpSdk on both sides. See references/transports.md.

Privy Server Wallets

createViemAccount from @privy-io/node/viem (needs @privy-io/node >= 0.20.0) returns a viem Account backed by a Privy server wallet, so it drops into tempo({ account }) wherever a local account would go.

Server-side signing works with app-owned server wallets; user-owned embedded wallets require authorization keys or key quorums. See references/typescript-sdk.md for the full setup and the manual toAccount() construction.

Testing & CLI

# Create an account (stored in keychain), then fund it on testnet
npx mppx account create
npx mppx account fund --network testnet

# Make a paid request
npx mppx http://localhost:3000/resource

# Parse a challenge without signing it
npx mppx sign --dry-run --challenge '<www-authenticate value>'

# Validate a server implementation end-to-end
npx mppx validate http://localhost:3000

The CLI also covers init, sessions (list/view/close), discover, services, mcp add, and skills add. Config comes from MPPX_CONFIG or an explicit --config - there is no auto-discovery from the working directory. Full reference: references/cli.md.

SDK Packages

| Language | Package | Install | |----------|---------|---------| | TypeScript | mppx | npm install mppx | | Python | pympp | pip install "pympp[tempo]" | | Rust | mpp | cargo add mpp --features tempo,client,server | | Ruby | mpp-rb (official, by Stripe) | see repo for gem name | | Go | mpp-go (official, by Tempo) | go get github.com/tempoxyz/mpp-go | | Elixir | mpp (community) | hex.pm/packages/mpp | | Swift | mpp-swift (community) | see repo |

Capability notes, checked against SDK source rather than the docs matrices (upstream publishes two that disagree):

  • Session intent: TypeScript and Rust only.
  • Proof Credentials (zero-dollar auth): TypeScript, Rust, and Ruby. Not pympp - the Python Tempo method implements only hash and transaction payload types.
  • Stripe, MCP, and event handling: TypeScript, Python, Rust, Ruby. Not the official mpp-go, which ships client/server/charge/fee-sponsorship/proof with net/http, Gin, Echo, and Chi middleware. A separate community Go mppx (cp0x) also exists.

Go and Ruby have first-class SDK doc pages at mpp.dev/sdk/go and mpp.dev/sdk/ruby.

Always import Mppx and tempo from the subpath matching your context (mppx/server, mppx/client, or the framework subpath). Note: Mppx and tempo are NOT exported from mppx/tempo - that subpath only exports Session and Ws. The authoritative subpath table is in references/typescript-sdk.md.

Key Concepts

  • Challenge/Credential/Receipt: The three protocol primitives. Challenge IDs are HMAC-SHA256 bound to prevent tampering. See references/protocol-spec.md
  • Split payments: One charge across multiple recipients in a single transaction (1-10 splits, per-split memos, expectedRecipients). See references/tempo-method.md
  • Fee sponsorship: Server pays gas on behalf of clients, capped by maxInFlightReservations / maxInFlightTotalFee
  • Relays: Delegate credential validation and broadcast to Tempo API or a compatible relay via tempo.charge({ relay })
  • Push/pull modes: Client broadcasts the transaction (push) or the server does (pull)
  • Client chain pinning: tempo.charge({ expectedChainId }) rejects challenges for the wrong Tempo network
  • Reusable client channels: pass a channelStore to persist and reuse payer session channels across processes
  • x402 interop: evm.charge({ x402: { facilitator } }) serves native MPP and x402 "exact" challenges from one route; the client prefers Payment-auth challenges
  • Custom methods: Implement any payment rail with Method.from(). See references/custom-methods.md

Payment Hooks

Attach logging, metrics, or tracing without touching the handler. Register on the object returned by Mppx.create(); each registration returns an unsubscribe function.

  • Server (mppx/server): onChallengeCreated, onPaymentSuccess, onPaymentFailed, onSessionSettlement, on('*')
  • Client (mppx/client): onChallengeReceived, onCredentialCreated, onPaymentResponse, onPaymentFailed

Server handlers are awaited inline on the request path - keep them fast. onPaymentFailed is the practical way to see the real error behind an opaque 402. See references/typescript-sdk.md and mpp.dev/advanced/payment-hooks.

Managing Agent Spend

Bound an agent's payment authority with Tempo access keys - delegated signing keys with built-in spend controls, their own expiry, and a revocation path.

import { Expiry } from 'accounts'
import { numberToHex, parseUnits } from 'viem'
import { Scopes } from 'viem/tempo'

const accessKey = {
  expiry: Expiry.days(7),
  limits: [{ token: usdc, limit: numberToHex(parseUnits('10', 6)), period: 86_400 }], // 10 USDC/day
  scopes: [Scopes.tip20(usdc).transfer({ recipients: [recipientAddress] })],
}
// Authorize: provider.request({ method: 'wallet_connect', params: [{ capabilities: { authorizeAccessKey: accessKey } }] })

Mppx.create({
  methods: [tempo({
    account: provider.getAccount(),
    ...provider.getMppxParameters({ accessKey: accessKeyAddress }),
  })],
})

Spend limits are hex-encoded - pass numberToHex(parseUnits(...)), not a raw bigint. Separate keys per app/tool/deployment keep delegated runtimes isolated. See mpp.dev/guides/managing-agent-spend and Tempo access keys.

Production Gotchas

The failure modes that cost the most time. Full detail in references/production-gotchas.md:

  • Tempo has no native gas token. Set feeToken or call setUserToken, or transactions fail with gas_limit: 0. "Fund with ETH" errors mean "fund with the stablecoin fee token"
  • Sessions do not settle themselves. Configure settlementSchedule or run your own tempo.settle() / tempo.settleBatch() sweep, paired with a close policy for idle channels - otherwise revenue accrues as unredeemed vouchers and channels stay open holding payer deposits
  • Charge settles before your handler runs. Use validateCredential then broadcastCredential when payment should depend on the work succeeding. Challenges expire after 5 minutes by default
  • Never use Store.memory() in production. Lost channel state means deposits stay reserved indefinitely
  • Set realm explicitly. Env vars outrank the per-request hostname, and Kubernetes HOSTNAME rotates every deploy, breaking mppscan attribution
  • Session voucher, close, and topUp credentials are bodyless POSTs, so a body validator running before mppx.session() rejects them with a spurious 400. Clone the request before reading its body, or mppx sees an empty one and returns 402
  • Large 402 headers overflow nginx's 4k default buffer and surface as 502

References

| File | Content | |------|---------| | references/protocol-spec.md | Challenge/Credential/Receipt, status codes, security | | references/typescript-sdk.md | mppx: server, client, middleware, transports, stores | | references/cli.md | mppx CLI: requests, validate, sign, accounts, config | | references/production-gotchas.md | Field-tested failure modes and their fixes | | references/sessions.md | Channels, vouchers, settlement, SSE/WS, recovery | | references/subscriptions.md | Subscription intent: activation, renewal, cancellation | | references/tempo-method.md | Tempo: fees, relays, push/pull, splits, sessions | | references/stripe-method.md | Stripe: SPT fiat flow, crypto deposit, Elements | | references/discovery-and-proxy.md | Proxy services, discovery documents, registries | | references/transports.md | HTTP, MCP, and WebSocket transport bindings | | references/python-sdk.md | pympp: @server.pay, async client, charge intent | | references/rust-sdk.md | mpp Rust: server/client, features, sessions | | references/lightning-method.md | Lightning: BOLT11 charge, bearer sessions, Spark | | references/custom-methods.md | Method.from, toClient, toServer patterns |

Official Resources