x402 Payments
Some MCP tools charge per call. When a priced tool is invoked without payment, the
server responds with 402 Payment Required and the SDK raises a
PaymentRequiredError. You can let the SDK pay automatically (autopay) or
catch the error and run your own flow (manual).
The wallet interface
Section titled “The wallet interface”Both paths need a wallet implementing McpWallet — it signs a payment and
returns a receipt; it does not broadcast (the facilitator settles server-side).
import { type McpWallet } from "@divinci-ai/mcp";
const wallet: McpWallet = { async getAddress() { return "0xYourWalletAddress"; }, async pay(details) { // details: { amountUsd, amountMicro, recipient, network, toolName, facilitatorUrl } const signed = await mySigner.signTransferAuthorization(details); return { transactionHash: signed.hash, network: details.network, recipient: details.recipient, amountUsd: details.amountUsd, timestamp: Date.now(), signature: signed.signature, }; },};Path A — Autopay
Section titled “Path A — Autopay”Configure payment on the client with a wallet and a per-call cap. The SDK
intercepts every 402, pays, and retries — callTool resolves with the eventual
success result. Calls that would exceed maxPaymentUsd raise
PaymentRequiredError instead of paying.
import { McpClient } from "@divinci-ai/mcp";
const mcp = new McpClient({ serverUrl: "https://mcp.divinci.app", payment: { autoPayment: true, maxPaymentUsd: 1.0, // hard cap per call network: "base", wallet, },});
await mcp.connect();
// No per-call awareness needed — the SDK pays + retries automatically.const result = await mcp.tools.callTool("premium_search", { query: "luxury watches" });console.log(result.content);You can also attach or change payment config after construction:
mcp.tools.setWallet(wallet);mcp.tools.setPaymentOptions({ autoPayment: true, maxPaymentUsd: 0.5 });Path B — Manual
Section titled “Path B — Manual”Leave autoPayment off (or omit payment) and handle the error yourself — useful
when you want a user to approve the spend, or the wallet lives elsewhere.
import { McpClient, PaymentRequiredError } from "@divinci-ai/mcp";
const mcp = new McpClient({ serverUrl: "https://mcp.divinci.app" });await mcp.connect();
async function callWithApproval(name: string, args: Record<string, unknown>) { try { return await mcp.tools.callTool(name, args); } catch (err) { if (!(err instanceof PaymentRequiredError)) throw err;
const { amountUsd, network, toolName } = err.paymentDetails; const ok = await confirmWithUser(`Pay $${amountUsd.toFixed(2)} on ${network} for ${toolName}?`); if (!ok) throw new Error("User declined payment");
// Retry this call with a one-off autopay override return await mcp.tools.callTool(name, args, { payment: { autoPayment: true, maxPaymentUsd: amountUsd, network, wallet }, }); }}
const result = await callWithApproval("premium_search", { query: "luxury watches" });PaymentRequiredError
Section titled “PaymentRequiredError”| Field | Type | Description |
|---|---|---|
paymentDetails.amountUsd | number | Required amount in USD. |
paymentDetails.recipient | string | Wallet that must receive payment. |
paymentDetails.network | string | "base" or "base-sepolia". |
paymentDetails.toolName | string | The tool that requires payment. |
paymentDetails.facilitatorUrl | string | Settlement facilitator endpoint. |
Discovering price up front
Section titled “Discovering price up front”const priced = (await mcp.tools.listTools()).filter((t) => t.pricing);const price = await mcp.tools.getToolPrice("premium_search");