npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@lucid-agents/mpp

v3.0.0

Published

Machine Payments Protocol (MPP) integration for Lucid Agents - multi-method payments via HTTP 402

Readme

@lucid-agents/mpp

Machine Payments Protocol (MPP) authorization for Lucid Agents. The extension uses the Payment-Auth wire format, delegates Tempo, Stripe, and EVM verification to mppx, and routes every adapter and Lucid task through the same authorization gate. Native EVM charges also accept compatible x402 v2 exact credentials without installing or configuring a second Lucid x402 seller.

MPP is compatible with the July draft-httpauth-payment-00 Internet-Draft, not an IETF standard. This package uses mppx 0.8.14 and implements a Lucid HTTP subset; it does not provide every MPP transport, discovery mechanism, rail, subscription, or session feature.

For focused, executable factories covering native Tempo, Stripe, and EVM charges, custom/Lightning descriptors, method negotiation, and Tempo sessions, see packages/examples/src/payment-methods/mpp.ts. The matching walkthrough is Every MPP payment method.

Built-in payment methods

import { createAgent } from '@lucid-agents/core';
import { http } from '@lucid-agents/http';
import { mpp, tempo } from '@lucid-agents/mpp';

const agent = await createAgent({ name: 'merchant', version: '1.0.0' })
  .use(
    mpp({
      config: {
        methods: [
          tempo.server({
            currency: '0x20c0000000000000000000000000000000000000',
            recipient: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
          }),
        ],
        secretKey: process.env.MPP_SECRET_KEY,
      },
    })
  )
  .use(http())
  .addEntrypoint({
    key: 'report',
    price: '0.05',
    paymentProtocol: 'mpp',
    handler: async () => ({ output: { report: '...' } }),
  })
  .build();

tempo.server(), stripe.server(), and evm.server() are materialized as native mppx charge methods. They validate the echoed HMAC challenge, credential schema, payment, and settlement before Lucid runs the entrypoint. Stripe also requires its Business Network profile:

Tempo charge accepts an explicit chainId and viem getClient resolver. Use them together to select a private, local, or otherwise non-default Tempo RPC without leaking transport ownership outside the MPP extension:

tempo.server({
  chainId: 31318,
  currency: '0x20c0000000000000000000000000000000000000',
  recipient: merchantAccount.address,
  getClient: ({ chainId }) => getTempoClient(chainId),
});
stripe.server({
  secretKey: process.env.STRIPE_SECRET_KEY!,
  networkId: process.env.MPP_STRIPE_NETWORK_ID!,
  currency: 'usd',
});

Tempo TIP-1034 sessions use the separate, explicit tempo.session() descriptor. The server account must be a signing viem Account; an address by itself is not sufficient for close and scheduled-settlement transactions.

import { tempo } from '@lucid-agents/mpp';
import { createSQLiteTempoSessionStore } from '@lucid-agents/mpp/storage/sqlite';

tempo.session({
  mode: 'production',
  account: merchantAccount,
  chainId: 4217,
  currency: '0x20c0000000000000000000000000000000000000',
  recipient: merchantAccount.address,
  decimals: 6,
  amount: '0.001',
  unitType: 'response',
  deposit: {
    minimum: '0.001',
    suggested: '0.10',
    maximum: '1.00',
  },
  store: createSQLiteTempoSessionStore('.data/tempo-sessions.db'),
  bootstrap: true,
  resolveChannelId: async ({ source, paymentRequest }) =>
    source ? lookupChannel(source, paymentRequest) : undefined,
  settlementSchedule: { units: 100, intervalMs: 60_000 },
  onSettlement: event => recordSettlement(event),
  getClient: ({ chainId }) => getTempoClient(chainId),
});

The minimum, suggested, and maximum deposits are enforced in the configured currency precision. Development may omit store to use bounded process-local memory; production rejects process-local storage. The SQLite and Postgres exports implement atomic updates so vouchers, top-ups, close/finalization, restart recovery, and concurrent deductions share one durable channel state.

For ordinary invokes, native verification deducts exactly one configured amount before the handler. Open and voucher credentials attached to billable requests can reach the handler; top-up, close, and non-billable management requests return their protocol response without invoking application code. Streaming uses the same channel and emits standard receipt and voucher-needed events while charging delivered units. The configured maximum deposit bounds the accounting reservation; final accounting is reconciled to delivered units.

Native Tempo end-to-end test

The required CI lane runs a public mppx buyer through Lucid's Hono HTTP surface and a real, digest-pinned Tempo development node. It verifies native charge settlement and the complete TIP-1034 session lifecycle, including invoke, SSE, top-up, SQLite restart/resume, and cooperative close:

bun run scripts/tempo-localnet.ts -- \
  bun test ./packages/examples/src/__tests__/tempo-localnet.e2e.ts

The orchestrator accepts only a loopback HTTP RPC with Tempo development chain ID 1337, attests the reviewed image source revision, redacts credentials from diagnostics, and removes its exact container on success or failure. It does not use a public RPC or cloud faucet.

An EVM descriptor names the EIP-3009 chain, token, recipient, precision, and exactly one settlement strategy:

import { evm, mpp } from '@lucid-agents/mpp';

mpp({
  config: {
    methods: [
      evm.server({
        chainId: 8453,
        currency: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
        recipient: '0xYourMerchantAddress',
        decimals: 6,
        authorization: { name: 'USD Coin', version: '2' },
        settlement: {
          type: 'facilitator',
          facilitator: 'https://facilitator.example',
        },
      }),
    ],
    secretKey: process.env.MPP_SECRET_KEY,
  },
});

For application-owned settlement, use settlement: { type: 'custom', settle: async context => ... }. The callback runs only after the EIP-3009 signature, route, body, amount, asset, recipient, and payer have been verified. It must return a durable settlement reference. The EVM rail accepts both Authorization: Payment ... and x402 v2 PAYMENT-SIGNATURE retries. Both paths share the same atomic challenge store, settlement strategy, verified payer/network metadata, and selected offer. Successful x402 retries add both Payment-Receipt and PAYMENT-RESPONSE.

Set a stable, high-entropy MPP_SECRET_KEY in production. If omitted, Lucid generates a new key for each process. The default outstanding-challenge and replay registry is process-local. Production services can inject a shared challengeStore, including the package's SQLite and Postgres adapters, so challenge leases and idempotent receipt recovery survive multiple instances or restarts. Challenge IDs are bounded, operation/body-bound, short-lived, and atomically claimed before verification to prevent concurrent replay. Lucid renews the active lease while native or custom verification is running and fences the result before consuming it. Custom MppChallengeStore implementations must make renew() atomic and return lost unless the same unexpired lease still owns the challenge.

Custom payment methods

Custom and Lightning descriptors require an application verifier:

import { custom, mpp } from '@lucid-agents/mpp';

mpp({
  config: {
    methods: [custom.server('acme-pay', { merchantId: 'merchant-42' })],
    currency: 'usd',
    async verifyCredential({ credential, requirement }) {
      const result = await verifyWithAcme({
        challenge: credential.challenge,
        payload: credential.payload,
        amount: requirement.amount,
      });
      return result.settled
        ? {
            valid: true,
            receipt: result.receipt,
            payer: result.payer,
            network: result.network,
          }
        : { valid: false, reason: 'Payment was not settled' };
    },
  },
});

The custom verifier is the trust boundary. It must validate the signature, amount, currency, recipient, method, settlement, and asserted payer. A custom method without a verifier always fails closed.

Verification occurs before target-side idempotency replay. A verifier that performs an externally visible settlement must also deduplicate it with the request's Idempotency-Key. Policy reservations and Lucid accounting happen only after the request wins a new target-side claim.

Custom verifier conformance

Provider integrations can run the reusable, runner-agnostic suite from the dedicated testing subpath:

import { custom } from '@lucid-agents/mpp';
import {
  runCustomMppHttpConformance,
  runCustomMppVerifierConformance,
  type CustomMppConformanceCredentialFactory,
  type CustomMppConformanceCredentialInspector,
} from '@lucid-agents/mpp/conformance';

const credentialFor: CustomMppConformanceCredentialFactory = async context => {
  const credential = await providerTestCredentials.create(context);
  return {
    payload: credential.payload,
    source: credential.source,
  };
};
const inspectCredential: CustomMppConformanceCredentialInspector = (
  credential,
  context
) =>
  normalizeActualProviderClaim(credential.payload, credential.source, context);

const report = await runCustomMppVerifierConformance({
  method: custom.server('acme-pay', { recipient: 'merchant-42' }),
  amount: '0.01',
  currency: 'usd',
  verifier: acmeVerifier,
  credentialFor,
  inspectCredential,
  expected: {
    receipt: value => value.startsWith('provider-test-'),
    payer: 'did:example:test-buyer',
    network: 'acme:test',
  },
  caseTimeoutMs: 5_000,
});

expect(report.passed).toBe(true);

The required inspectCredential adapter maps each actual provider payload to the public valid/invalid, issued/other, required/other, current/expired, and settled/unsettled vocabulary. It is trusted test code: parse the unsigned claim and verify its signature directly, and never infer evidence from context.scenario. The runner checks that projection against the named scenario and rejects duplicate serialized payload/source pairs. Create independent cases for authenticity, challenge, intent, amount, currency, recipient, method, payer, expiry, and unsettled state. Challenge and intent fixtures should otherwise carry valid provider signatures over the tampered claims; one generic malformed credential does not prove those checks.

Every provider fixture and verifier call is bounded by caseTimeoutMs (five seconds by default, at most sixty seconds). On verifier timeout the runner aborts the verifier request signal and Lucid consumes the ambiguous credential. Providers must honor that signal where possible and still deduplicate durable settlement, because an external operation may complete after local timeout. Run the suite only against an isolated test account: it exercises successful settlement, retries, and ambiguous failures.

To verify the full public transport lifecycle, supply isolated service adapters for success, handler failure, and settlement failure:

const httpReport = await runCustomMppHttpConformance({
  serviceFor: scenario => createProviderTestService({ scenario }),
  expected: {
    receipt: value => value.startsWith('provider-test-'),
    successfulAccountingCount: 2,
    successfulAccountingTotal: '20000',
  },
  forbiddenResponseFragments: [process.env.PROVIDER_TEST_SECRET!],
});

Each service adapter sends its own protected invoke/stream requests, creates valid, invalid-authenticity, expired, and wrong-context credentials from the returned challenge, and reports normalized handler, settlement, accounting, and live/staged reservation counters. The runner verifies 402 negotiation, successful receipts, exactly-once invoke and stream accounting, handler non-invocation for rejected credentials, paid handler failure behavior, provider timeout fencing, redacted settlement failure, zero leaked reservations, and replay without a second settlement. Every failure body and every response header, including Payment-Receipt, is scanned for each required non-empty forbiddenResponseFragments marker. This lets any provider run the same public HTTP checks without depending on Hono, Lucid internals, or the bundled reference method.

The verifier owns credential authenticity and validity checks, verifies all challenge and payer fields before settlement, and returns only a durable, non-secret receipt. An externally visible settlement must be idempotent under the validated Idempotency-Key (falling back to the challenge ID for single-use credentials). Provider timeouts and exceptions are ambiguous: Lucid consumes the credential to preserve at-most-once settlement, while the provider must retain enough durable state to reconcile the outcome.

Production replay recovery requires a stable challenge secret and durable challenge store shared by every replica. Provider settlement deduplication must also be durable; the conformance runner's process-local harness is not a production storage recommendation. Never put API keys, raw credentials, signatures, request bodies, provider exception text, or settlement secrets in receipts, logs, conformance reports, or discovery metadata.

Before advertising a custom method as E2E verified, retain the passing conformance report, protected invoke and streaming results, redacted receipt and accounting evidence, SDK version, provider sandbox/network, execution date, and documented limitations. A passing descriptor/conformance run proves the custom extension contract, not a native implementation of the underlying payment rail.

lightning.server() remains a custom descriptor governed by these same rules. This suite does not claim native Lightning settlement or node interoperability; that requires a separately scoped regtest integration and product contract.

Wire and replay contract

Challenges are standard responses. A priced operation configured with multiple methods emits one challenge per compatible method in stable server order:

HTTP/1.1 402 Payment Required
WWW-Authenticate: Payment id="...", realm="...", method="tempo", intent="charge", request="...", expires="..."
WWW-Authenticate: Payment id="...", realm="...", method="stripe", intent="charge", request="...", expires="..."
WWW-Authenticate: Payment id="...", realm="...", method="evm", intent="charge", request="...", expires="..."

Clients can filter and rank those offers with Accept-Payment, including wildcards and HTTP q-values:

Accept-Payment: evm/charge, stripe/charge;q=0.8, tempo/charge;q=0

Absent or malformed preferences preserve server order. If a syntactically valid header matches no configured method, Lucid deterministically ignores it and emits the normal server offers. A retry is always dispatched by the method/intent in its signed challenge; a changed Accept-Payment header cannot downgrade it to another verifier.

Clients retry with a standard credential:

Authorization: Payment <base64url-credential>

Malformed, unknown, expired, wrong-target, replayed, and verifier-rejected credentials fail closed. Successful responses carry Payment-Receipt. decodeMppCredential() is intentionally decode-only and is never sufficient for authorization.

OpenAPI discovery

The HTTP extension serves base-path-aware OpenAPI 3.1 discovery at /openapi.json. Paid invoke and stream operations include ordered x-payment-info.offers derived from the same resolver used for live challenges. The document describes Accept-Payment, Payment credentials, WWW-Authenticate, Payment-Receipt, and RFC 9457 Problem Details without serializing recipients, API keys, verifier secrets, or other method config. When an EVM offer is present, discovery also describes the x402 PAYMENT-REQUIRED, PAYMENT-SIGNATURE, and PAYMENT-RESPONSE headers.

Protocol-neutral HTTP integrations can compose the same projection directly:

const operationPayment = agent.mpp.projectPayment(entrypoint, 'invoke');
const paymentComponents = agent.mpp.openApiComponents();

const document = agent.mpp.projectOpenApi({
  title: 'Merchant agent',
  version: '1.0.0',
  basePath: '/api/agent',
  entrypoints: agent.entrypoints.snapshot(),
});

projectPayment() is the composable per-operation API. projectOpenApi() is a standalone document projection for transports that do not already own an OpenAPI builder.

Entrypoint overrides

.addEntrypoint({
  key: 'session',
  price: { invoke: '0.001', stream: '0.0001' },
  paymentProtocol: 'mpp',
  metadata: {
    mpp: {
      intent: 'session',
      methods: ['acme-session'],
      description: 'Metered research session',
    },
  },
  handler: async () => ({ output: {} }),
})

The acme-session descriptor in this example must be configured through custom.server() with an application verifier as described above.

If x402 and MPP are both installed, every priced entrypoint must select paymentProtocol: 'x402' | 'mpp'.

Outbound calls

Pass native client intents from mppx/client:

import { tempo } from 'mppx/client';

const paidFetch = await agent.mpp.getMppFetch({
  methods: [tempo({ account })],
});

const response = await paidFetch?.('https://merchant.example/report');

Lucid creates mppx with polyfill: false, so globalThis.fetch is never replaced. A custom Fetch implementation can be supplied as fetch.

Environment helper

mppFromEnv(overrides) reads MPP_METHOD, MPP_CURRENCY, MPP_DEFAULT_INTENT, MPP_CHALLENGE_EXPIRY, MPP_SECRET_KEY, MPP_REALM, Tempo recipient/currency settings, and Stripe secret/network settings. Any explicit custom verifier is preserved.

MPP contracts are defined only in @lucid-agents/types/mpp; this package does not duplicate or re-export them.

Migrating to this release

MppRuntime requires hasCredential(request), projectPayment(), openApiComponents(), and projectOpenApi(). Custom runtime implementations must perform only canonical credential detection there; the method must not verify or settle a payment. The built-in mpp() extension uses the same decoder as authorization, including comma-separated Authorization schemes.

A successful custom verifyCredential result must now include a non-empty serialized receipt:

return settled
  ? { valid: true, receipt: settlement.receipt }
  : { valid: false, reason: 'Payment was not settled' };

Custom challenge stores must also implement atomic renewable verification leases through MppChallengeStore.renew(). The claim and renewal results include renewAfterMs, which lets the runtime refresh leases before another worker may reclaim them.

The receipt must already be an exact, legal HTTP header value no larger than 8 KiB: leading or trailing whitespace, control characters, and oversized values are rejected. Once a verifier claims success, an invalid receipt consumes that credential and returns a service error rather than retrying the verifier, because settlement may already be irreversible.

This is a breaking contract change. Lucid fails closed when a verifier claims success without a usable receipt because task admission cannot report a coherent post-settlement outcome otherwise.