Server half of the x402 MCP transport: gates a tool handler behind payment.
call/3 inspects an MCP tool-call request for a payment in
_meta["x402/payment"] and drives the full verify → execute → settle flow
against an X402.Facilitator:
- No payment → the payment-required tool result (
isError: truewith thePaymentRequiredobject instructuredContentandcontent[0].text). - Invalid payment (wrong version,
acceptednot matching the advertised requirements, extension echo mismatch, failed verification) → the same payment-required result with the rejection reason. - Valid payment → the wrapped handler runs; on success the payment is
settled and the receipt is attached to result
_meta["x402/payment-response"]. When settlement fails after execution, only the payment error is returned — never the tool's content.
The module is MCP-library agnostic: requests and results are plain maps in the shapes MCP libraries already use, so the wrapper drops into any tool dispatch function. See the MCP guide for integration snippets.
Example
config =
X402.MCP.Server.init(
tool: "premium_search",
accepts: [
%{
price: "10000",
network: "eip155:84532",
asset: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
pay_to: "0x209693Bc6afc0C5328bA36FaF03C514EF312287C",
extra: %{"name" => "USDC", "version" => "2"}
}
],
facilitator: MyApp.Facilitator
)
X402.MCP.Server.call(request, config, fn _request ->
%{"content" => [%{"type" => "text", "text" => "results..."}]}
end)Replay protection
Pass payment_identifier_cache: (an
X402.Extensions.PaymentIdentifier.ETSCache server, the same option
X402.Plug.PaymentGate takes) to atomically claim each payment proof before
settlement, rejecting concurrent or repeated submissions of the same signed
payment. The claim is released when the handler fails or settlement fails,
so the client may retry with the same payment.
Summary
Types
An MCP tool-call handler: request params in, tool result map out.
Functions
Gates an MCP tool-call request behind x402 payment verification.
Validates and compiles paid-tool options.
Builds the payment-required tool result advertised by this configuration.
Types
An MCP tool-call handler: request params in, tool result map out.
@type options() :: %{ tool: String.t(), facilitator: X402.Facilitator.server(), hooks: module(), payment_identifier_cache: X402.Extensions.PaymentIdentifier.ETSCache.server() | nil, accepts: [map()], resource: map(), extensions: map() }
Configuration map produced by init/1.
Functions
Gates an MCP tool-call request behind x402 payment verification.
request is the tool-call params map (typically with "name",
"arguments", and "_meta" keys). handler receives the request and must
return a tool result map (with a "content" list and optional
"isError"); it only runs after the payment has been verified.
Always returns a tool result map:
- the payment-required result when payment is missing, invalid, rejected by the facilitator, or already settled (replay)
- the handler's result with the settlement receipt attached to
_meta["x402/payment-response"]on success - the handler's error result unchanged (no settlement) when the handler
sets
"isError" => true - the settlement-failure result (payment-required format, without the tool's content) when settlement fails after execution
- an opaque internal error result when the facilitator transport fails
If the handler raises, the replay claim is released and the exception is re-raised for the MCP library to surface.
Validates and compiles paid-tool options.
Raises NimbleOptions.ValidationError for invalid options and
ArgumentError when :accepts is empty, an accept's extra.paymentFlow
is not "authorization", or the advertised data cannot be encoded as JSON.
Options
:tool(String.t/0) - Required. Tool name; used for the defaultmcp://tool/{tool}resource URL.:accepts(list ofmap/0) - Required. Payment options advertised inPaymentRequired.accepts(at least one).:facilitator(term/0) - Facilitator server pid/name used for verification and settlement. The default value isX402.Facilitator.:hooks- Lifecycle hook module implementingX402.Hooks. The default value isX402.Hooks.Default.:payment_identifier_cache- Optional idempotency cache: anETSCacheserver pid/name (the default adapter), or a{module, cache}adapter tuple implementingX402.Extensions.PaymentIdentifier.Cache. When set, the wrapper performs an atomic claim (viaput_new) on a hash of the signed scheme payload before settling, preventing concurrent requests from double-settling the same payment. The default value isnil.:resource_url- Custom ResourceInfo.url (defaults tomcp://tool/{tool}). The default value isnil.:description- ResourceInfo.description (defaults toTool: {tool}). The default value isnil.:mime_type(String.t/0) - ResourceInfo.mimeType. The default value is"application/json".:service_name- ResourceInfo.serviceName (printable ASCII, max 32 characters recommended). The default value isnil.:tags(list ofString.t/0) - ResourceInfo.tags (max 5 recommended). The default value is[].:icon_url- ResourceInfo.iconUrl (absolute http(s) URL). The default value isnil.:extensions- Protocol extensions advertised in PaymentRequired.extensions. The default value is%{}.
Accept option fields (inside :accepts)
:scheme- Payment scheme (exactorupto). The default value is"exact".:price- Required. Payment amount in atomic token units (PaymentRequirementsamount).:network(String.t/0) - Required. Blockchain network in CAIP-2 format (for exampleeip155:84532).:asset(String.t/0) - Required. Token contract address or asset identifier.:pay_to(String.t/0) - Required. Recipient wallet address (payToin the PaymentRequirements schema).:max_timeout_seconds(pos_integer/0) - Maximum time allowed for payment completion. The default value is60.:extra- Scheme-specific extra fields (string or atom keys). The default value is%{}.
Builds the payment-required tool result advertised by this configuration.
Useful for advertising the price of a paid tool outside call/3 (for
example in a tools/list response or documentation).