Workshop Duration: 10 minutes
This tutorial walks through the Ampersend TypeScript SDK for integrating x402 payment capabilities into MCP (Model Context Protocol) applications. The x402 protocol extends HTTP with a standard way for servers to request payment before providing resources, using the 402 "Payment Required" status code. By the end of this tutorial, you'll understand how to build MCP clients that can pay for tools, MCP servers that charge for tools, and proxy servers that add payments to existing MCP infrastructure.
- Overview
- Core Concepts
- Wallets
- Treasurers
- MCP Client (Buyer)
- MCP Server (Seller)
- MCP Proxy
- Complete Example
The Ampersend SDK enables x402 micropayments for MCP tool calls using blockchain technology. This allows AI applications to pay for premium tools, data, and services in real-time without human intervention, enabling a new economy of machine-to-machine transactions. Payments are made in USDC stablecoin on Base (an Ethereum L2), providing fast, low-cost transactions with real USD value.
The SDK provides:
-
Wallets – Sign payment authorizations using cryptographic keys. Wallets hold the private keys needed to authorize spending from your blockchain account, supporting both traditional Ethereum accounts (EOA) and advanced smart contract accounts with programmable spending rules.
-
Treasurers – Decide whether to approve or reject payment requests based on your policies. The treasurer acts as a financial gatekeeper, evaluating each payment request against budgets, spend limits, or other business logic before authorizing the wallet to sign.
-
MCP Client – A drop-in replacement for the standard MCP client that automatically handles x402 payment flows. When a tool call returns a 402 response, the client seamlessly coordinates with your treasurer to create and submit payment, then retries the request.
-
MCP Proxy – A transparent proxy server that adds x402 payment capability to any MCP server without modifications. Deploy the proxy in front of existing MCP servers to let clients pay for tools, or use it to centralize payment handling for multiple backend servers.
-
FastMCP Middleware – Server-side middleware for FastMCP that lets you add payment requirements to any tool. Wrap your tool's execute function to require payment, verify submitted payments, and settle them on-chain.
┌─────────────────┐ ┌─────────────────┐
│ MCP Client │ │ MCP Server │
│ (Buyer) │ │ (Seller) │
│ │ │ │
│ ┌───────────┐ │ HTTP │ ┌───────────┐ │
│ │ Treasurer │◄─┼─────────┼─►│ x402 │ │
│ └─────┬─────┘ │ 402 │ │ Middleware│ │
│ │ │ │ └───────────┘ │
│ ┌─────▼─────┐ │ │ │
│ │ Wallet │ │ │ │
│ └───────────┘ │ │ │
└─────────────────┘ └─────────────────┘
The diagram above shows the separation of concerns: the Treasurer makes policy decisions about whether to pay, while the Wallet handles the cryptographic signing. On the server side, the x402 Middleware handles payment requirements, verification, and settlement.
The X402Wallet interface defines the contract for creating signed payment payloads from payment requirements. A payment payload contains all the cryptographic data needed for a seller to verify and settle the payment on the blockchain. Different wallet implementations support different account types, but they all produce the same standard payment payload format.
import type { X402Wallet } from "@ampersend_ai/ampersend-sdk/x402"
interface X402Wallet {
createPayment(requirements: PaymentRequirements): Promise<PaymentPayload>
}The PaymentRequirements parameter contains details like the payment amount, recipient address, asset type (USDC), and network. The wallet uses this information to construct an ERC-3009 authorization (a gasless approval) that allows the facilitator to transfer funds on behalf of the payer.
The X402Treasurer interface separates payment decisions from payment creation, implementing the Strategy pattern for payment authorization. This design allows you to swap out different payment policies (naive, budget-limited, API-controlled) without changing the rest of your code. The treasurer receives the full context of what's being purchased, enabling intelligent decisions about when to pay.
import type { X402Treasurer, Authorization, PaymentStatus } from "@ampersend_ai/ampersend-sdk/x402"
interface X402Treasurer {
// Called when payment is required - return null to decline
onPaymentRequired(
requirements: ReadonlyArray<PaymentRequirements>,
context?: PaymentContext
): Promise<Authorization | null>
// Called with status updates (sending, accepted, rejected, error)
onStatus(
status: PaymentStatus,
authorization: Authorization,
context?: PaymentContext
): Promise<void>
}The onPaymentRequired method is called when a server requests payment, giving you the opportunity to inspect the requirements and decide whether to proceed. The onStatus method provides lifecycle notifications so you can track payment outcomes for logging, analytics, or reconciliation purposes.
When a treasurer approves a payment, it returns an Authorization object that bundles the signed payment with a tracking identifier. The authorization ID enables correlation between the initial payment decision and subsequent status updates. This is essential for audit trails and debugging payment flows.
interface Authorization {
payment: PaymentPayload // Signed payment data
authorizationId: string // For tracking status
}The payment payload is a JSON-serializable structure containing the ERC-3009 signature and metadata. The authorization ID should be unique per payment attempt—using crypto.randomUUID() is the standard pattern in TypeScript.
The AccountWallet is designed for externally owned accounts (EOAs), which are standard Ethereum wallets controlled by a single private key. This is the simplest wallet type and is ideal for development, testing, or scenarios where a single key controls all spending. EOA wallets sign payments directly using the private key without any additional smart contract logic.
import { AccountWallet } from "@ampersend_ai/ampersend-sdk/x402"
// From private key
const wallet = AccountWallet.fromPrivateKey("0x...")
// Or from viem Account
import { privateKeyToAccount } from "viem/accounts"
const account = privateKeyToAccount("0x...")
const wallet = new AccountWallet(account)
// Create payment
const payment = await wallet.createPayment(requirements)The static fromPrivateKey factory is the most convenient way to create an AccountWallet. Under the hood, it uses viem's privateKeyToAccount to create an account object and wraps it in the wallet. When you call createPayment(), the wallet constructs an ERC-3009 receiveWithAuthorization message and signs it with your private key.
The SmartAccountWallet supports Safe smart accounts with session keys, enabling advanced features like programmable spending limits and multi-signature requirements. Smart accounts are smart contracts that can validate signatures according to custom logic, using the ERC-1271 standard. This wallet type is recommended for production deployments where you need on-chain spending controls.
import { SmartAccountWallet } from "@ampersend_ai/ampersend-sdk/x402"
const wallet = new SmartAccountWallet({
smartAccountAddress: "0x...", // Smart account address
sessionKeyPrivateKey: "0x...", // Session key
chainId: 84532, // Base Sepolia
validatorAddress: "0x...", // Optional: OwnableValidator
})
// Create payment (uses ERC-1271 signatures)
const payment = await wallet.createPayment(requirements)The session key is a subordinate key that's been granted limited signing authority by the smart account. Unlike a master key, session keys can be revoked and typically have time or amount limits. The chainId must match your target network (84532 for Base Sepolia testnet, 8453 for Base mainnet).
Smart accounts support:
-
Spend limits enforced on-chain – The blockchain itself enforces maximum spending amounts, providing trustless protection against bugs or compromised keys. Even if your application is hacked, on-chain limits prevent excessive spending.
-
Session keys with limited permissions – Delegate signing authority to temporary keys that can be easily rotated or revoked. Session keys can have time-based expiration, ensuring that even leaked keys become useless after a set period.
-
Multi-sig requirements – Require multiple parties to approve large transactions, adding a human-in-the-loop for high-value payments. This is useful for team-managed applications where no single person should have unilateral control.
-
Social recovery – Recover account access through trusted guardians if keys are lost. Unlike EOAs where losing your private key means losing everything, smart accounts can be recovered through a predefined social process.
The createWalletFromConfig factory function provides a unified way to create wallets from configuration objects. This is particularly useful when reading wallet configuration from environment variables or configuration files, as you can switch between EOA and smart account wallets without changing code.
import { createWalletFromConfig } from "@ampersend_ai/ampersend-sdk/x402"
// EOA wallet
const eoaWallet = createWalletFromConfig({
type: "eoa",
privateKey: "0x..."
})
// Smart Account wallet
const smartWallet = createWalletFromConfig({
type: "smartAccount",
smartAccountAddress: "0x...",
sessionKeyPrivateKey: "0x...",
chainId: 84532
})The type discriminator tells the factory which wallet implementation to use. This pattern works well with TypeScript's discriminated unions, giving you type safety on the configuration object based on the type you specify.
The NaiveTreasurer auto-approves all payment requests without any checks, making it ideal for testing and development scenarios. It immediately creates a payment for any valid request, which is useful when you trust the seller and want to focus on testing other parts of your integration. Never use this in production with real funds unless you fully trust all possible sellers.
import { NaiveTreasurer, AccountWallet } from "@ampersend_ai/ampersend-sdk/x402"
const wallet = AccountWallet.fromPrivateKey("0x...")
const treasurer = new NaiveTreasurer(wallet)
// Automatically approves all payments
const auth = await treasurer.onPaymentRequired(requirements)The naive treasurer simply takes the first payment requirement from the array and creates a payment for it. It doesn't implement any budget tracking, rate limiting, or approval logic—it's the simplest possible treasurer implementation.
Or use the factory function for a more concise setup:
import { createNaiveTreasurer } from "@ampersend_ai/ampersend-sdk/x402"
const treasurer = createNaiveTreasurer({
type: "eoa",
privateKey: "0x..."
})The factory function combines wallet and treasurer creation into a single call. It accepts the same wallet configuration options as createWalletFromConfig and returns a ready-to-use treasurer.
The AmpersendTreasurer integrates with the Ampersend API to provide enterprise-grade payment controls and monitoring. Before approving any payment, it checks with the Ampersend backend to verify the payment falls within configured spend limits. This provides a centralized dashboard for managing application spending across your organization.
Integrates with Ampersend API for:
-
Spend limits (daily/monthly) – Configure maximum spending amounts that reset on a daily or monthly basis. The API tracks cumulative spending and rejects payments that would exceed limits, providing budget guardrails for autonomous applications.
-
Payment monitoring – Every payment decision is logged and visible in the Ampersend dashboard. This gives you real-time visibility into what your applications are spending money on, with detailed breakdowns by seller, time period, and payment type.
-
Budget controls – Define different budgets for different applications or use cases, with alerts when spending approaches limits. You can also set up approval workflows for payments above certain thresholds.
import { createAmpersendTreasurer } from "@ampersend_ai/ampersend-sdk/ampersend"
const treasurer = createAmpersendTreasurer({
apiUrl: "https://api.staging.ampersend.ai",
walletConfig: {
type: "smartAccount",
smartAccountAddress: "0x...",
sessionKeyPrivateKey: "0x...",
chainId: 84532
}
})
// Now payments are:
// 1. Authorized against API spend limits
// 2. Tracked and monitored
// 3. Reported for audit trailThe factory function creates both the API client and wallet internally, handling SIWE (Sign-In with Ethereum) authentication automatically. The apiUrl should point to the Ampersend API—use the staging URL for development and testing.
Creating a custom treasurer lets you implement any payment logic specific to your use case. Below is a budget-tracking treasurer that enforces a daily spending limit locally, without requiring an external API. This pattern is useful for simple scenarios or when you want to keep payment logic entirely within your codebase.
import type { X402Treasurer, Authorization, PaymentStatus, X402Wallet } from "@ampersend_ai/ampersend-sdk/x402"
class BudgetTreasurer implements X402Treasurer {
private spentToday = 0
constructor(
private wallet: X402Wallet,
private dailyLimitUsd: number
) {}
async onPaymentRequired(
requirements: ReadonlyArray<PaymentRequirements>,
context?: PaymentContext
): Promise<Authorization | null> {
const req = requirements[0]
const amountUsd = Number(req.maxAmountRequired) / 1_000_000 // USDC decimals
// Check budget
if (this.spentToday + amountUsd > this.dailyLimitUsd) {
console.log(`Budget exceeded: $${this.spentToday} + $${amountUsd} > $${this.dailyLimitUsd}`)
return null // Decline
}
// Approve and create payment
const payment = await this.wallet.createPayment(req)
this.spentToday += amountUsd
return {
payment,
authorizationId: crypto.randomUUID()
}
}
async onStatus(status: PaymentStatus, auth: Authorization): Promise<void> {
console.log(`Payment ${auth.authorizationId}: ${status}`)
}
}This example demonstrates the key concepts: extracting the payment amount from requirements, checking against a limit, and returning null to decline or an Authorization to approve. In production, you might persist the spentToday value to a database and implement proper daily reset logic with a scheduled job.
The Client class is a drop-in replacement for the standard MCP Client with transparent payment support. When a tool call or resource read receives a 402 response, this client automatically coordinates with your treasurer to decide on payment, creates the payment payload, and retries the request. Your application code doesn't need to know about the payment flow at all.
import { Client } from "@ampersend_ai/ampersend-sdk/mcp/client"
import { AccountWallet, NaiveTreasurer } from "@ampersend_ai/ampersend-sdk/x402"
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"
// Setup
const wallet = AccountWallet.fromPrivateKey("0x...")
const treasurer = new NaiveTreasurer(wallet)
// Create client
const client = new Client(
{ name: "MyApp", version: "1.0.0" },
{
mcpOptions: { capabilities: { tools: {} } },
treasurer
}
)
// Connect
const transport = new StreamableHTTPClientTransport(new URL("http://localhost:8000/mcp"))
await client.connect(transport)
// Call tools - payments are handled automatically!
const result = await client.callTool({ name: "add", arguments: { a: 5, b: 3 } })The client takes your app info (name and version, used in MCP initialization) and options including the treasurer. The mcpOptions are passed through to the underlying MCP client—capabilities: { tools: {} } enables tool calling. Once connected via a transport, all tool calls and resource reads will automatically handle payment flows.
Understanding the payment flow helps with debugging and designing your payment policies. The flow is designed to be invisible to application code while providing hooks for treasurers at key decision points.
-
Client calls tool on server – Your application makes a normal MCP tool call, like
client.callTool({ name: "add", arguments: { a: 5, b: 3 } }). At this point, no payment information is attached to the request. -
Server returns
402 Payment Requiredwith requirements – The server's x402 middleware intercepts the request and responds with an MCP error containing payment requirements. Requirements specify the price, accepted assets (USDC), network (Base), and recipient address. -
Client calls
treasurer.onPaymentRequired()– The client extracts the payment requirements from the 402 error and presents them to your treasurer for a decision. The treasurer can inspect the amount, tool name, arguments, and context before deciding. -
If approved, payment is attached and request retried – The treasurer creates the payment through the wallet, and the client automatically retries the original tool call with the payment in the request's
_meta["x402/payment"]field. -
Server verifies payment and executes tool – The server's middleware validates the payment signature and settles it (either immediately or deferred). Only after successful verification does the tool execute.
-
Client calls
treasurer.onStatus()with result – Whether the payment was accepted, rejected, or errored, the treasurer receives a status update. This enables logging, budget tracking adjustments, or custom error handling.
The withX402Payment middleware wraps FastMCP tool execute functions to add payment requirements. This is the easiest way to monetize your MCP tools—just wrap your execute function and define when payment is required and how to settle it. The middleware handles all the x402 protocol details, error responses, and payment verification.
import { withX402Payment } from "@ampersend_ai/ampersend-sdk/mcp/server/fastmcp"
import { useFacilitator } from "x402/verify"
import { FastMCP } from "fastmcp"
import { z } from "zod"
const server = new FastMCP({
name: "Paid Calculator",
version: "1.0.0"
})
// Payment requirements
const requirements = {
asset: "0x036CbD53842c5426634e7929541eC2318f3dCF7e", // USDC on Base Sepolia
scheme: "exact",
network: "base-sepolia",
payTo: "0xYourAddress",
maxAmountRequired: "1000", // $0.001 USDC
resource: "http://localhost:8080/api/add",
mimeType: "application/json",
maxTimeoutSeconds: 300,
description: "Add two numbers",
extra: { name: "USDC", version: "2" }
}
// Add paid tool
server.addTool({
name: "add",
description: "Add two numbers",
parameters: z.object({
a: z.number(),
b: z.number()
}),
execute: withX402Payment({
// Check if payment required
onExecute: async ({ args }) => {
return requirements // Return requirements to require payment
// return null // Return null for free execution
},
// Verify and settle payment
onPayment: async ({ payment, requirements }) => {
return useFacilitator({ url: "https://x402.org/facilitator" })
.settle(payment, requirements)
}
})(async (args) => {
return `${args.a + args.b}`
})
})
// Free tool (no payment required)
server.addTool({
name: "echo",
description: "Echo a message",
parameters: z.object({ message: z.string() }),
execute: async (args) => args.message
})
// Start server
await server.start({
transportType: "httpStream",
httpStream: { port: 8080, endpoint: "/mcp" }
})The requirements object specifies what payment you accept. The asset is the token contract address (USDC on Base Sepolia shown), payTo is your recipient address, and maxAmountRequired is the price in the token's smallest unit (1000 = $0.001 USDC since USDC has 6 decimals). The extra field with name and version is required for ERC-3009 domain separation.
The middleware options give you full control over when payment is required and how to process it. The onExecute callback runs first to determine if payment is needed, and onPayment handles verification and settlement when payment is provided.
interface WithX402PaymentOptions {
// Return requirements to require payment, or null for free
onExecute: (context: { args: unknown }) => Promise<PaymentRequirements | null>
// Verify and settle the payment
onPayment: (context: {
payment: PaymentPayload
requirements: PaymentRequirements
}) => Promise<SettleResponse | void>
}The onExecute callback receives the tool arguments and can make dynamic decisions about pricing. For example, you might charge more for complex queries or offer free usage for certain argument patterns. Returning null skips payment entirely and executes the tool for free.
The onPayment callback receives both the submitted payment and the original requirements. Use a facilitator service like x402.org to verify the payment signature and settle it on-chain. You can also return void to accept the payment without settlement (useful for testing).
The MCP Proxy adds x402 payment capability to any MCP server without modifying the server's code. This is perfect for adding payments to third-party MCP servers, centralizing payment handling across multiple backends, or gradually rolling out payments without changing existing infrastructure.
The proxy can be started from the command line with minimal configuration. Set your wallet's private key in an environment variable, start the proxy, and point your clients at the proxy URL with the original server as the target parameter.
# Set environment
export BUYER_PRIVATE_KEY=0x...
# Start proxy
pnpm --filter ampersend-sdk proxy:dev
# Connect clients to proxy:
# http://localhost:8402/mcp?target=http://original-server:8000/mcpThe proxy maintains session state between requests, ensuring that payment flows work correctly across multiple tool calls. Each unique target URL can have independent sessions, so you can proxy to multiple backend servers simultaneously.
For more control, you can initialize the proxy server programmatically with a custom treasurer. This lets you integrate with your existing application startup logic and use any treasurer implementation.
import { initializeProxyServer } from "@ampersend_ai/ampersend-sdk/mcp/proxy"
import { createNaiveTreasurer } from "@ampersend_ai/ampersend-sdk/x402"
const treasurer = createNaiveTreasurer({
type: "eoa",
privateKey: "0x..."
})
const { server } = await initializeProxyServer({
transport: { port: 8402 },
treasurer
})
// Now proxy requests:
// POST http://localhost:8402/mcp?target=http://actual-server/mcpThe initializeProxyServer function returns the server instance so you can later call server.stop() for graceful shutdown. The proxy validates the target URL to prevent SSRF attacks and only allows HTTP/HTTPS targets.
The proxy creates a bridge between the client and server transports, intercepting messages in both directions. When it sees a 402 response from the server, it consults the treasurer, creates a payment, and forwards a new request with payment attached.
┌────────────┐ ┌────────────┐ ┌────────────┐
│ MCP Client │───►│ x402 Proxy │───►│ MCP Server │
│ │ │ │ │ │
│ │◄───│ + Payment │◄───│ 402 │
│ │ │ Handling │ │ │
│ │───►│ │───►│ │
│ │◄───│ │◄───│ Success │
└────────────┘ └────────────┘ └────────────┘
The beauty of this approach is that neither the client nor the server needs to know about x402—the proxy handles everything. The client sees normal MCP responses (possibly with a small delay for payment), and the server just sees requests with valid payment headers.
This complete example shows how to use x402-enabled MCP tools with LangChain to create an AI agent that can pay for premium tools. The agent uses OpenAI's GPT-4o-mini model and automatically handles payments when calling paid tools on the MCP server.
import { Client } from "@ampersend_ai/ampersend-sdk/mcp/client"
import { AccountWallet, NaiveTreasurer } from "@ampersend_ai/ampersend-sdk/x402"
import { createReactAgent } from "@langchain/langgraph/prebuilt"
import { loadMcpTools } from "@langchain/mcp-adapters"
import { ChatOpenAI } from "@langchain/openai"
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"
async function main() {
// 1. Setup payment
const wallet = AccountWallet.fromPrivateKey(process.env.PRIVATE_KEY as `0x${string}`)
const treasurer = new NaiveTreasurer(wallet)
// 2. Create x402 MCP client
const client = new Client(
{ name: "langchain-agent", version: "1.0.0" },
{ mcpOptions: { capabilities: { tools: {} } }, treasurer }
)
// 3. Connect to server
const transport = new StreamableHTTPClientTransport(
new URL(process.env.MCP_SERVER_URL!)
)
await client.connect(transport)
// 4. Load MCP tools into LangChain
const tools = await loadMcpTools("server", client, {
throwOnLoadError: true,
prefixToolNameWithServerName: false
})
// 5. Create LangChain agent
const model = new ChatOpenAI({ modelName: "gpt-4o-mini" })
const agent = createReactAgent({ llm: model, tools })
// 6. Run query - payments handled automatically!
const response = await agent.invoke({
messages: [{ role: "user", content: "What is 5 plus 3?" }]
})
console.log(response.messages.at(-1)?.content)
await client.close()
}
main()The @langchain/mcp-adapters package bridges MCP tools to LangChain's tool format. The loadMcpTools function discovers available tools from the server and wraps them as LangChain tools. When the LLM decides to call a tool, LangChain invokes it through our x402-enabled client, which handles any payment requests transparently.
npm install @ampersend_ai/ampersend-sdk
# or
pnpm add @ampersend_ai/ampersend-sdkThe SDK requires Node.js 18+ and has peer dependencies on viem for Ethereum operations. TypeScript types are included—no separate @types package needed.
The SDK uses subpath exports to allow importing only what you need, keeping bundle sizes small. Each subpath is a distinct entry point with its own dependencies.
// Main exports
import { initializeProxyServer } from "@ampersend_ai/ampersend-sdk"
// Core x402
import {
AccountWallet,
SmartAccountWallet,
NaiveTreasurer,
createNaiveTreasurer,
createWalletFromConfig
} from "@ampersend_ai/ampersend-sdk/x402"
// MCP Client
import { Client, X402Middleware } from "@ampersend_ai/ampersend-sdk/mcp/client"
// MCP Proxy
import { initializeProxyServer } from "@ampersend_ai/ampersend-sdk/mcp/proxy"
// FastMCP Server
import { withX402Payment, createX402Execute } from "@ampersend_ai/ampersend-sdk/mcp/server/fastmcp"
// Ampersend API
import { ApiClient, createAmpersendTreasurer } from "@ampersend_ai/ampersend-sdk/ampersend"
// Smart Account utilities
import { encode1271Signature, OWNABLE_VALIDATOR } from "@ampersend_ai/ampersend-sdk/smart-account"The /x402 subpath contains the core abstractions used by both client and server code. The /mcp/client and /mcp/server/fastmcp subpaths contain protocol-specific implementations. The /ampersend subpath is for Ampersend API integration.
| Variable | Description |
|---|---|
BUYER_PRIVATE_KEY |
EOA private key (0x...) for simple wallet setups. This key signs all payment authorizations, so keep it secure. Only use for development or if you don't need smart account features. |
BUYER_SMART_ACCOUNT_ADDRESS |
Smart account address (0x...) that holds funds and authorizes payments. Create one at app.ampersend.ai. This is the address that actually owns the tokens being spent. |
BUYER_SMART_ACCOUNT_KEY_PRIVATE_KEY |
Session key private key (0x...) for smart account signing. This subordinate key has limited authority granted by the smart account, reducing risk if compromised. |
AMPERSEND_API_URL |
Ampersend API base URL for spend limit integration. Use https://api.staging.ampersend.ai for development or https://api.ampersend.ai for production. |
CDP_API_KEY_ID |
Coinbase Developer Platform API key ID for using the Coinbase facilitator. Required if you want to use Coinbase's infrastructure for payment settlement. |
CDP_API_KEY_SECRET |
Coinbase Developer Platform API key secret. Pair this with CDP_API_KEY_ID for Coinbase facilitator authentication. |
FACILITATOR_URL |
Custom facilitator URL if not using Coinbase. Defaults to https://x402.org/facilitator which is a public facilitator for testing. |
This diagram shows the complete request-response flow including payment handling. The bracketed items show internal operations that happen automatically.
┌──────────┐ ┌──────────┐
│ Client │ │ Server │
└────┬─────┘ └────┬─────┘
│ │
│ 1. callTool(add, {a:5,b:3}) │
│──────────────────────────────►│
│ │
│ 2. 402 Payment Required │
│◄──────────────────────────────│
│ │
│ [Treasurer.onPaymentRequired]│
│ [Wallet.createPayment] │
│ │
│ 3. callTool + payment │
│──────────────────────────────►│
│ │
│ [Server settles] │
│ │
│ 4. Result: "8" │
│◄──────────────────────────────│
│ │
│ [Treasurer.onStatus] │
│ │
From the application's perspective, you just call callTool and get a result. All the payment negotiation happens transparently in steps 2-3, with your treasurer making the authorization decision and your wallet creating the cryptographic payment proof.
-
Get testnet USDC: Visit https://faucet.circle.com/ and select Base Sepolia to receive free test USDC. You'll need to connect a wallet that holds some testnet ETH for the initial faucet transaction. This USDC has no real value but lets you test the full payment flow end-to-end.
-
Create an agent: Sign up at https://app.staging.ampersend.ai to create a smart account with configurable spend limits. The dashboard will generate a session key for you and let you configure daily/monthly budgets. Fund your smart account with the testnet USDC from step 1.
-
Run examples: Start with the included examples to see everything working together:
# Start server (in one terminal) pnpm --filter fastmcp-x402-server dev # Run LangChain example (in another terminal) pnpm --filter langchain-mcp dev "What is 5 plus 3?"
The server will start on port 8080, and the LangChain example will connect, discover tools, and use them with automatic payment.
Questions? Check the x402 specification for protocol details and the official reference implementation.