Skip to content

deszhou/mpp-go

Repository files navigation

mpp-go

Go Reference CI License: MIT

Go SDK for the Machine Payments Protocol (MPP) — inline HTTP 402 payments for APIs and AI agents.

What is MPP?

MPP lets API servers charge per-request or per-session using standard HTTP headers — no payment dashboard, no API keys, no billing setup on the client. The protocol extends HTTP 402 with a WWW-Authenticate: Payment challenge/response flow:

Client                              Server
  │                                   │
  │──── GET /api/data ───────────────>│
  │                                   │  (no Authorization header)
  │<─── 402 Payment Required ─────────│
  │     WWW-Authenticate: Payment     │
  │       realm="my-api"              │
  │       method="tempo"              │
  │       intent="charge"             │
  │       request="<base64url-json>"  │
  │                                   │
  │  [client pays / signs credential] │
  │                                   │
  │──── GET /api/data ───────────────>│
  │     Authorization: Payment        │
  │       challenge=<challenge>       │
  │       payload=<credential>        │
  │                                   │
  │<─── 200 OK ───────────────────────│
  │     Payment-Receipt: Payment ...  │

mpp-go provides:

  • Server middleware for net/http, Gin, Echo, and Fiber
  • Client that auto-handles 402 challenges and retries
  • Payment methods: Tempo (on-chain EVM), Stripe, Mock (testing)
  • Session payments (payment channels / pay-as-you-go)
  • SSE streaming with metered voucher flow
  • MCP integration for AI agent payments (Model Context Protocol)

Installation

go get github.com/deszhou/mpp-go

For Ethereum/Tempo on-chain verification, build with the geth tag:

go build -tags geth ./...

Quick Start

Server — Mock (no real payment, good for development)

package main

import (
    "net/http"

    netmpp "github.com/deszhou/mpp-go/http/nethttp"
    "github.com/deszhou/mpp-go/methods/mock"
    "github.com/deszhou/mpp-go/server"
)

func main() {
    mppServer := server.New(
        server.WithMethod(&mock.ServerMethod{}),
        server.WithRealm("my-api"),
    )

    charge := server.StaticCharge(mock.MethodName, "1000000", "pathUSD", nil) // 1.00 USD
    http.Handle("/api/data", netmpp.Middleware(mppServer, charge)(myHandler))
    http.ListenAndServe(":8080", nil)
}

Server — Tempo (on-chain EVM, requires -tags geth)

package main

import (
    "net/http"

    netmpp "github.com/deszhou/mpp-go/http/nethttp"
    "github.com/deszhou/mpp-go/methods/tempo"
    "github.com/deszhou/mpp-go/server"
    "github.com/deszhou/mpp-go/store"
)

func main() {
    verifier := tempo.NewEthVerifier() // requires -tags geth
    mppServer := server.New(
        server.WithMethod(tempo.NewServerMethod(verifier,
            tempo.WithRecipient("0xYourAddress"),
            tempo.WithNetworkID(42431),
        )),
        server.WithStore(store.NewMemoryStore()),
        server.WithRealm("my-api"),
        server.WithSecretKey("your-secret-key"),
    )

    charge := server.StaticCharge("tempo", "1000000", "pathUSD", nil) // 1.00 USD
    http.Handle("/api/data", netmpp.Middleware(mppServer, charge)(myHandler))
    http.ListenAndServe(":8080", nil)
}

Client

The client automatically handles 402 challenges and retries with payment:

package main

import (
    "context"
    "fmt"

    mppClient "github.com/deszhou/mpp-go/client"
    "github.com/deszhou/mpp-go/methods/mock"
)

func main() {
    c := mppClient.NewHTTPClient(
        []mppClient.PaymentProvider{
            &mock.ClientProvider{WalletAddress: "0xYourAddress"},
        },
    )

    resp, err := c.Get(context.Background(), "https://api.example.com/data")
    fmt.Println(resp.Status, err)
}

For Tempo on-chain payments (requires -tags geth):

signer := tempo.NewEthSigner(privateKey)
c := mppClient.NewHTTPClient(
    []mppClient.PaymentProvider{tempo.NewClientProvider(signer)},
)

Reading the Receipt

After payment verification, the receipt is available from the request context:

func myHandler(w http.ResponseWriter, r *http.Request) {
    receipt := netmpp.ReceiptFromContext(r.Context())
    fmt.Fprintf(w, "paid via %s, ref: %s", receipt.Method, receipt.Reference)
}

Framework Middleware

All middleware adapters share the same behavior: no Authorization header → issue 402; invalid credential → reissue 402; valid payment → call next handler with receipt in context.

Gin

import ginmpp "github.com/deszhou/mpp-go/http/gin"

r := gin.New()
r.Use(ginmpp.Middleware(mppServer, charge))
r.GET("/api/data", func(c *gin.Context) {
    receipt := ginmpp.ReceiptFromContext(c)
    c.JSON(200, gin.H{"receipt": receipt})
})

Echo

import echompp "github.com/deszhou/mpp-go/http/echo"

e := echo.New()
e.Use(echompp.Middleware(mppServer, charge))
e.GET("/api/data", func(c echo.Context) error {
    receipt := echompp.ReceiptFromContext(c)
    return c.JSON(200, receipt)
})

Fiber

import fibermpp "github.com/deszhou/mpp-go/http/fiber"

app := fiber.New()
app.Use(fibermpp.Middleware(mppServer, charge))
app.Get("/api/data", func(c *fiber.Ctx) error {
    receipt := fibermpp.ReceiptFromContext(c)
    return c.JSON(receipt)
})

Dynamic Pricing

Use a ChargeFunc closure for per-request pricing:

charge := func(r *http.Request) (protocol.MethodName, string, string, *server.ChargeOptions, error) {
    amount := computePrice(r) // your pricing logic
    return "tempo", amount, "pathUSD", nil, nil
}

Session Payments (Pay-as-you-go)

Session payments use payment channels for streaming or multi-request billing. The client opens a channel with an initial deposit, then sends signed vouchers for each unit consumed.

// Server: register a session method
mppServer := server.New(
    server.WithSessionMethod(tempoSession.NewServerMethod(channelStore)),
)

// Server: verify each request
result, _, err := mppServer.VerifySession(ctx, r.Header.Get("Authorization"))
if result.ManagementResponse != nil {
    // lifecycle event (Open / TopUp / Close) — return to client
    json.NewEncoder(w).Encode(result.ManagementResponse)
    return
}
// proceed: payment channel has sufficient balance

SSE Metered Streaming

For streaming APIs billed by token or chunk:

import "github.com/deszhou/mpp-go/sse"

// Server sends SSE events; client pays per voucher
srv := sse.NewServer(mppServer, channelStore)
srv.ServeHTTP(w, r) // handles voucher negotiation automatically

MCP Integration (AI Agents)

For Model Context Protocol tool servers, mpp-go provides JSON-RPC helpers:

import "github.com/deszhou/mpp-go/mcp"

// Extract payment challenge from a -32042 JSON-RPC error
challenge, err := mcp.ParsePaymentRequiredError(jsonRPCErr)

// Attach credential to tool call params
params = mcp.AttachCredential(params, credential)

Packages

Package Description
github.com/deszhou/mpp-go/server Core server: Mpp, challenge/verify, charge & session
github.com/deszhou/mpp-go/client HTTP client with auto-retry on 402
github.com/deszhou/mpp-go/protocol Wire types: challenge, credential, receipt, headers
github.com/deszhou/mpp-go/methods/tempo Tempo on-chain payment method (charge + session)
github.com/deszhou/mpp-go/methods/stripe Stripe Payment Intent method
github.com/deszhou/mpp-go/methods/mock In-memory mock for testing
github.com/deszhou/mpp-go/http/nethttp net/http middleware
github.com/deszhou/mpp-go/http/gin Gin middleware
github.com/deszhou/mpp-go/http/echo Echo middleware
github.com/deszhou/mpp-go/http/fiber Fiber middleware
github.com/deszhou/mpp-go/sse SSE event types and metered streaming
github.com/deszhou/mpp-go/mcp MCP (Model Context Protocol) payment helpers
github.com/deszhou/mpp-go/digest HTTP body digest (sha-256=<base64>)
github.com/deszhou/mpp-go/mperr RFC 9457 Problem Details error types
github.com/deszhou/mpp-go/store Challenge store (memory + channel state)
github.com/deszhou/mpp-go/expires RFC 3339 expiry timestamp helpers

Build Tags

Tag Effect
geth Enables tempo.NewEthVerifier() and tempo.NewEthSigner() via go-ethereum

Without geth, on-chain Ethereum operations are unavailable but all other packages compile normally. This keeps the default dependency footprint small.

Protocol

mpp-go implements the Machine Payments Protocol specification:

  • HTTP 402 + WWW-Authenticate: Payment realm="…" method="…" intent="…" request="…"
  • HMAC-SHA256 challenge IDs (32-byte, base64url) — wire-compatible with mpp-rs
  • JCS (RFC 8785) canonical JSON encoding for payment requests
  • RFC 9457 Problem Details error responses
  • RFC 3339 timestamps for challenge expiry

Examples

See the examples/ directory:

Example Description
examples/server net/http server with mock payment
examples/client Client that auto-pays 402 challenges
examples/tempo Tempo flow with mock EVM signer/verifier — run with go run ./examples/tempo (no build tags). Real RPC via NewEthSigner / NewEthVerifier needs -tags geth.

Run the mock examples locally:

go run ./examples/server &
go run ./examples/client
go run ./examples/tempo

Contributing

Pull requests are welcome. For significant changes, open an issue first to discuss the approach.

go test ./...
go test -tags geth ./...
go vet ./...

License

MIT

About

Golang SDK for the Machine Payments Protocol

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages