# `X402.MCP.Client`
[🔗](https://github.com/cardotrejos/x402/blob/v0.9.0/lib/x402/mcp/client.ex#L1)

Client half of the x402 MCP transport: pay for tool calls automatically.

`call/3` drives an arbitrary tool-call function (any MCP client library, or
a plain function in tests) through the x402 detect → sign → retry-once loop:

1. Perform the tool call. A result that is not payment-required is returned
   as-is.
2. Extract the `PaymentRequired` object from the payment-required tool
   result (or from a `402`/`-32042` JSON-RPC error).
3. With `:siwx` configured and a `sign-in-with-x` challenge advertised,
   validate its domain and exact URI pins, obtain `:on_payment_required`
   consent, then sign it and retry the call with the proof in
   `_meta["x402/sign-in-with-x"]` and no payment; a result that is not
   payment-required is returned as-is.
4. A fresh payment-required result needs fresh consent before either its
   SIWX proof or payment is signed. Without SIWX, obtain consent before payment.
5. Build and sign a payment via `X402.Client.build_payment/3`, reserve
   its amount against `:budget` (when given), and retry the tool call once
   with the payload in request `_meta["x402/payment"]`.
6. Return the retried result with the decoded settlement receipt from
   result `_meta["x402/payment-response"]`, when present.

A tool call is **never paid twice**: at most one payment retry is made, a
second payment-required result is returned as-is, and requests that already
carry `_meta["x402/payment"]` are refused.

## Example

    {:ok, signer} = X402.Signer.LocalKey.new(System.fetch_env!("PAYER_KEY"))

    request = %{"name" => "premium_search", "arguments" => %{"query" => "x402"}}

    {:ok, %{result: result, payment_response: receipt, paid: true}} =
      X402.MCP.Client.call(request, &MyMCP.call_tool/1,
        signer: signer,
        max_amount: "10000",
        on_payment_required: fn payment_required ->
          IO.inspect(payment_required["accepts"], label: "about to pay")
          :ok
        end
      )

The tool-call function receives the (possibly payment-carrying) request map
and may return the tool result map directly, `{:ok, result}`, or
`{:error, reason}`.

## Spend controls

Cap what an automated payer signs with `:max_amount` (per payment),
`:policies` (`X402.Client.Policy` filters), and `:budget` (an
`X402.Client.Budget` shared across calls; the reservation is released
when the paid retry comes back as another payment-required result
without a successful receipt). With none of them configured a warning is
logged once.

## Sign-In-With-X

With `siwx: [chain_id: :auto, domain: "mcp.example.com",
uri: "https://mcp.example.com/tools/premium_search"]` the client
answers a `sign-in-with-x` challenge advertised in the payment-required
result before paying: the tool call is retried with the proof in
`_meta["x402/sign-in-with-x"]` (see `X402.MCP.put_siwx/2`) and no
payment. A result that is not payment-required is returned with
`siwx_authenticated: true`; another payment-required result continues
with the payment flow, the paid call carrying a proof for the new
challenge. The callback does not expose a verifiable transport origin, so
both `:domain` and an exact HTTP(S) `:uri` pin are required. Missing or
mismatched pins fail with `:domain_mismatch` / `:uri_mismatch` before consent,
wallet signing, or retry. Never derive these pins from the challenge.

The caller must bind `call_fun` to the independently trusted server (for
example, a fixed HTTPS endpoint or trusted local process). Pins constrain
the signed audience; they cannot authenticate an arbitrary callback or
prevent a malicious transport from relaying challenges for that same audience.
`:on_payment_required` can veto each challenge before either kind of signature.

# `call_error`

```elixir
@type call_error() ::
  :payment_already_attempted
  | :payment_cancelled
  | :invalid_tool_result
  | {:transport_error, term()}
  | {:siwx, X402.Client.SIWX.reason()}
  | X402.Client.Budget.reserve_error()
  | X402.Client.build_error()
```

# `call_fun`

```elixir
@type call_fun() :: (map() -&gt; map() | {:ok, map()} | {:error, term()})
```

A tool-call function driven by `call/3`.

# `response`

```elixir
@type response() :: %{
  result: map(),
  payment_response: map() | nil,
  paid: boolean(),
  siwx_authenticated: boolean()
}
```

A completed tool call.

`:result` is the final tool result map; `:payment_response` holds the
decoded settlement receipt from `_meta["x402/payment-response"]` when the
server sent one, otherwise `nil`; `:paid` tells whether a payment was
signed and submitted; `:siwx_authenticated` tells whether the server
accepted a Sign-In-With-X proof instead of a payment.

# `build_payment_meta`
*since 0.6.0* 

```elixir
@spec build_payment_meta(map(), X402.Signer.t(), keyword()) ::
  {:ok, %{required(String.t()) =&gt; map()}} | {:error, X402.Client.build_error()}
```

Builds the request `_meta` map paying for a payment-required response.

Accepts either a decoded `PaymentRequired` map or the payment-required tool
result that carries one, selects and signs a payment option via
`X402.Client.build_payment/3`, and returns the `_meta` entries to merge into
the retried tool-call request. Options are forwarded to
`X402.Client.build_payment/3`.

Use this instead of `call/3` when your MCP library exposes request `_meta`
but you want to drive the retry yourself.

# `call`
*since 0.6.0* 

```elixir
@spec call(map(), call_fun(), keyword()) :: {:ok, response()} | {:error, call_error()}
```

Performs a tool call, paying for the tool if it requires payment.

See the module documentation for the full flow. Returns `{:ok, response()}`
with the final tool result, or `{:error, reason}` when the payment was
cancelled, could not be built, or the tool-call function failed.

## Options

* `:signer` - Required. A struct implementing `X402.Signer`, used to sign the payment.

* `:network` (`t:String.t/0`) - Payment selection filter — see `X402.Client.select_requirements/2`.

* `:scheme` (`t:String.t/0`) - Payment selection filter — see `X402.Client.select_requirements/2`.

* `:asset` (`t:String.t/0`) - Payment selection filter — see `X402.Client.select_requirements/2`.

* `:max_amount` - Maximum `amount` (atomic units) this client will pay — the budget guard
  for automated payers. Requirements above it are never selected.

* `:policies` (list of function of arity 2) - Selection policies forwarded to `X402.Client.select_requirements/2` —
  see `X402.Client.Policy`. The default value is `[]`.

* `:budget` - An `X402.Client.Budget` the selected amount is reserved against
  before the paid retry is made. See `X402.Client.Budget` for what
  counts as spent. The default value is `nil`.

* `:hooks` - `X402.Client.Hooks` module forwarded to `X402.Client.build_payment/3`. The default value is `X402.Client.Hooks.Default`.

* `:siwx` - Automatic Sign-In-With-X: a keyword list of `X402.Client.SIWX`
  options (`chain_id:` required, or `:auto`; `domain:` and `uri:` required
  when a challenge is advertised).
  `nil`/`false` disables it. The default value is `nil`.

* `:valid_after_buffer` (`t:non_neg_integer/0`) - Clock-skew buffer for the authorization's `validAfter`, in seconds. The default value is `60`.

* `:auth_capture` - Auth-capture signing options forwarded to `X402.Client.build_payment/3`. The default value is `[]`.

* `:on_payment_required` - Budget/consent hook invoked with the decoded `PaymentRequired` map
  once per challenge before any SIWX proof or payment is signed. Return `:cancel` to abort with
  `{:error, :payment_cancelled}`; any other return value continues. The default value is `nil`.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
