Skip to content

Modes: headless & supervised

The MCP server runs in one of two modes. Mode A (headless) is the default: the spend policy you configure is the consent, so the agent pays autonomously up to its caps with no per-payment prompt. Mode B (supervised) is opt-in: it additionally asks a human to approve each fund-moving payment, in the host’s own UI, at the moment of spend.

Mode B is additive: it layers on top of the policy and never replaces it. The budget gate always runs first; the confirmation only ever sees a payment that already passed it. There is no way to confirm your way past a spend cap.

With no extra configuration, the server is headless. Every payment passes through the spend policy before any on-chain send; if it’s within the caps, it settles without asking.

{
"env": {
"PIPRAIL_PRIVATE_KEY": "0xYOUR_PRIVATE_KEY",
"PIPRAIL_CHAIN": "base",
"PIPRAIL_MAX_AMOUNT": "0.10", // per-payment ceiling
"PIPRAIL_MAX_TOTAL": "10.00" // lifetime ceiling per token
}
}

A payment that breaches the policy is refused before any funds move. The piprail_pay_request tool doesn’t throw. It returns a structured refusal the model can read and reason about (the SDK raises a PaymentDeclinedError, which the tool funnels into this object):

{
"ok": false,
"declined": true,
"code": "PAYMENT_DECLINED",
"reasonCode": "POLICY", // typed cause the model can branch on
"reason": "payment of 100000 base units exceeds policy.maxAmount (0.10 USDC).",
"explain": "payment of 100000 base units exceeds policy.maxAmount (0.10 USDC)."
}

reasonCode is the typed half (POLICY / BUDGET / OUTSIDE_WINDOW / SESSION_EXPIRED / APPROVAL); reason is the SDK’s human sentence, derived from the true on-chain decimals so a server can’t slip past a cap by understating its price.

Set PIPRAIL_CONFIRM=1 to wire the SDK’s onBeforePay seam to the MCP server.elicitInput() call. Now, after a payment clears the policy, the host (Claude Desktop, Cursor, …) shows a human a one-line approval form, and no funds move unless they explicitly check approve.

{
"env": {
"PIPRAIL_PRIVATE_KEY": "0xYOUR_PRIVATE_KEY",
"PIPRAIL_CHAIN": "base",
"PIPRAIL_CONFIRM": "1"
}
}

The prompt reads, for example (the network is shown as its CAIP-2 id, so eip155:8453 is Base):

Approve paying 0.10 USDC to api.example.com on eip155:8453?

Decline, cancel, or ignore it and the payment comes back as a clean refusal, { ok: false, declined: true, code: "PAYMENT_DECLINED", reasonCode: "APPROVAL" }, with nothing spent.

The confirmation form is built only from the quote’s own non-secret fields. It is secret-free by construction: no wallet key, no RPC URL, not even the full URL, only the host (the path can carry query-string secrets).

ShownWithheld
Amount + token symbolWallet key / seed
Recipient host (not the full URL)RPC URL
Network (chain)Query-string params
An unverified-token warning, where the symbol can’t be confirmed on-chainnone

Mode B refuses by default. Only an explicit approval authorises a payment; everything else, decline, cancel, a round-trip timeout, a dropped transport, a validation error, or any throw inside elicitInput, maps to not paying.

OutcomeResult
Explicit approvePayment settles
Decline / cancel{ declined: true, reasonCode: "APPROVAL", … }
Timeout / transport drop / error{ declined: true, reasonCode: "APPROVAL", … }

The approval window defaults to 55 seconds, deliberately just under the MCP client’s 60-second call timeout, so a slow human times out inside the elicitation and the decline reaches the agent as a clean refusal rather than an opaque transport error. To give a human longer to deliberate, raise both PIPRAIL_CONFIRM_TIMEOUT_MS and your MCP client’s own request timeout.

{ "env": { "PIPRAIL_CONFIRM": "1", "PIPRAIL_CONFIRM_TIMEOUT_MS": "120000" } }

Degrades silently on clients that can’t elicit

Section titled “Degrades silently on clients that can’t elicit”

Elicitation is a client capability. The server detects it at pay-time: a host that doesn’t advertise the elicitation form (or any elicitation at all) silently degrades to Mode A. The payment proceeds under the policy alone, and the agent is never blocked by a prompt the host can’t render. Mode B never weakens the budget; degrading just drops the extra ask.

  • Leave it unset for budgeted. The policy is the consent and nothing prompts. This is what every config written before modes existed already does.
  • supervised when a human should approve each spend. Setting it turns confirmation on; PIPRAIL_CONFIRM=1 is the same thing said the other way, and setting them to disagree is refused at boot rather than quietly resolved.
  • sovereign when the agent genuinely owns the wallet: it adds swapping, selling (piprail_sell / piprail_collect / piprail_earnings) and piprail_wallet. It requires PIPRAIL_MAX_PER_SWAP, because a payment cap counts payments and a swap is not one.

A model can never set its own mode. It is read from the environment the operator controls.

Mode A, headlessMode B, supervised
TriggerdefaultPIPRAIL_CONFIRM=1
Per-payment human approvalnoyes (where the host can elicit)
Consent modelthe spend policythe policy plus a human
Best forunattended agents, CI, serversa person at the keyboard, larger or rare spends

Mode A suits an agent running unattended within a tight budget. Mode B suits a human in the loop who wants eyes on each spend, and it falls back to Mode A automatically wherever the host can’t ask. In both modes the spend policy is the floor that always holds.