<!--
Sitemap:
- [Paybox](/index): The non-custodial wallet for AI agents
- [Getting started](/getting-started)
- [Credentials & agents](/concepts/model): The Paybox model
- [Approvals & passkeys](/concepts/approvals)
- [Request lifecycle](/concepts/requests): From intent to result
- [MCP connector](/connect/mcp)
- [OAuth 2.1](/connect/oauth)
- [MCP tools](/reference/mcp-tools): What an agent can call
- [API & endpoints](/api-reference)
- [SDK & CLI](/sdk-cli)
-->

# MCP tools \[What an agent can call]

These are the tools the Paybox MCP server exposes to a connected agent. Once an
agent holds a scoped access token (see **[OAuth 2.1](/connect/oauth)**) it calls
them over the MCP endpoint at `https://api.paybox.sh/mcp` (streamable HTTP,
protocol version `2025-06-18`).

Every tool acts **on behalf of the signed-in user**, scoped to the credentials
they granted this client. An agent never sees a raw card, key share, or secret —
it gets a **scoped output** (a one-time card, a signature, a short-lived token),
and anything sensitive can pause for the user's passkey.

:::info
The agent only ever calls the tools below. Signing a wallet operation happens in
the **[signing window](/concepts/requests#the-signing-window)** — an in-chat UI
the host renders — not in agent code. The internal `submit_*` / `moonx_*` tools
the window uses are hidden from the model and are not part of this surface.
:::

## The result envelope

Most write tools return a status-tagged result. Branch on `status`:

| `status` | Meaning | What to do |
| --- | --- | --- |
| `success` | Completed. `output` carries the result. For on-chain operations this means **confirmed on-chain**, never merely broadcast. | Use `output`. |
| `pending_approval` | The user must approve with their passkey. | Surface `approval_url`, then poll `get_request`. |
| `pending_signature` | Cleared to sign (autonomous, or already approved). The signing window finishes it. | Poll `get_request` for the artifact. |
| `pending_settlement` | A cross-chain swap's source tx is broadcast; the bridge is settling on the destination chain. | Keep polling `get_request`. |
| `pending_confirmation` | Broadcast but not yet confirmed on-chain. | Keep polling `get_request`; it flips to `success` (confirmed) or `error` (dropped). |
| `denied` | Policy or the user rejected it. | Read `reason`; do not retry blindly. |
| `error` | Something failed (not a policy decision). | Read `message`. |

The golden rule: **never re-call a write tool to "finish" it** — that starts a
new operation. Always poll **[`get_request`](#get_request)** with the
`request_id` you got back. See **[Request lifecycle](/concepts/requests)** for
the full state machine.

***

## list\_credentials

Discover the credentials this client may use. Call it first — every other tool
takes a `credential_id` from here.

**Input:** none.

**Returns:**

```json
{
  "credentials": [
    {
      "credential_id": "9f8e…",
      "name": "Personal Visa",
      "kind": "card",
      "metadata": { "brand": "VISA", "last4": "4242", "basis_theory_token_id": "…" },
      "approval_mode": "always_approve"
    },
    {
      "credential_id": "1a2b…",
      "name": "Trading wallet",
      "kind": "wallet",
      "metadata": {
        "provider_wallet_id": "b3f1…",
        "chains": ["evm"],
        "address": "0x70a0…2b1f"
      },
      "approval_mode": "autonomous"
    },
    {
      "credential_id": "7c6d…",
      "name": "OpenAI key",
      "kind": "secret",
      "metadata": { "encrypted": true },
      "approval_mode": "always_approve"
    }
  ],
  "ungranted_summary": {
    "wallet": { "evm": 1, "solana": 0 },
    "card": 0,
    "secret": 0
  }
}
```

* `kind` is `card` | `wallet` | `secret`.
* `approval_mode` is `always_approve` (every op needs a passkey) or `autonomous`
  (acts within the grant — see **[the model](/concepts/model)**).
* `ungranted_summary` contains only counts by credential kind and wallet family.
  It never includes an ungranted credential's ID, name, address, last4, or
  metadata. A non-zero count means the credential exists in Paybox but has not
  been granted to this connector.
* `metadata` is public display data only:
  * **wallet** → `address`, `provider_wallet_id`, and `chains` — the chain
    **family** (`["evm"]` or `["solana"]`), not a per-chain list. An EVM MPC
    wallet shares one address across every `eip155:*` chain. `address` may be
    `null` for older wallets until it's captured.
  * **card** → `brand`, `last4`, `basis_theory_token_id` (brand/last4 are
    client-attested and unverified — never authorize on them).
  * **secret** → `{ "encrypted": true }`.

***

## request\_payment

Authorize issuance of a merchant-scoped, one-time virtual card for a **card**
credential. This does **not** submit the merchant checkout, charge the merchant,
or top up an account by itself.

| Field | Type | Notes |
| --- | --- | --- |
| `credential_id` | string | A `card`-kind credential from `list_credentials`. |
| `merchant` | string | Merchant identifier — shown to the user at approval and in the audit log. |
| `merchant_url` | string | The real HTTPS origin Basis Theory binds the one-time card to. Don't synthesize it from the name. |
| `amount_cents` | integer | Amount in cents. |
| `currency` | string | ISO 4217. Currently `USD` only. |

**Returns** the [result envelope](#the-result-envelope). On `success`, `output`
carries the one-time virtual-card details when immediately available. For
human-approved/cardholder-verified payments, `get_request` intentionally
redacts the card after approval; call
[`claim_payment_credentials`](#claim_payment_credentials) once to get the
usable card. The agent must then use those card details at the merchant's
checkout/API and only report the purchase complete after the merchant confirms
the payment or credit top-up.

***

## claim\_payment\_credentials

Claim the usable card details of an **approved** payment. For human-approved
payments the polled request redacts the card, so this is the one call that
returns it — **one-time**: the claim is consumed, and a second call fails.

| Field | Type | Notes |
| --- | --- | --- |
| `request_id` | string | An approved `request_payment` request. |

**Returns** a credential output (`output_type: "payment_token"`) whose `value`
carries the one-time virtual-card details, plus its `expires_at`. Claim it only
when you're ready to pay at the merchant's checkout.

***

## request\_wallet\_sign

Sign with a **wallet** credential. You pass *what* to sign as an `intent` —
**not** a pre-hashed digest. Paybox builds the correct bytes and the
[signing window](/concepts/requests#the-signing-window) signs it client-side; the
private key never leaves MoonX MPC.

| Field | Type | Notes |
| --- | --- | --- |
| `credential_id` | string | A `wallet`-kind credential. |
| `intent` | object | What to sign — see the intent table below. The chain, destination, and value are read from the intent itself; there are no separate declared fields. |

The `intent` is tagged by an `op` field:

| `op` | Shape | Result on success |
| --- | --- | --- |
| `message` | `{ "op": "message", "message": "…" }` | EIP-191 `{ "signature": "0x…" }` |
| `typedData` | `{ "op": "typedData", "typedData": { … } }` | EIP-712 `{ "signature": "0x…" }` |
| `transaction` | `{ "op": "transaction", "transaction": { …eip1559… } }` | `{ "serializedTransaction": "0x…" }` (ready for `eth_sendRawTransaction`) |
| `authorization` | `{ "op": "authorization", "authorization": { … } }` | EIP-7702 `{ "authorization": { … } }` |
| `solanaMessage` | `{ "op": "solanaMessage", "address": "<base58>", "message": "…" }` | `{ "signature": "<hex>" }` |
| `solanaTransaction` | `{ "op": "solanaTransaction", "address": "<base58>", "transactionBase64": "…" }` | `{ "signedTransactionBase64": "…" }` |
| `raw` | `{ "op": "raw", "rawSigningPayloadHex": "0x…" }` | Signs a pre-computed digest as-is (escape hatch). |

**Returns** the [result envelope](#the-result-envelope):

* `pending_signature` — autonomous: the signing window can sign right away.
* `pending_approval` — the user approves with their passkey **first**; surface
  `approval_url`, then the window signs.

Either way, **poll `get_request`** for the signed artifact in `output`.

```json
// example call
{
  "credential_id": "1a2b…",
  "intent": { "op": "message", "message": "Sign in to Acme #4821" }
}
```

***

## request\_secret

Reveal a **secret** credential (e.g. an API key) so the agent can use it.

| Field | Type | Notes |
| --- | --- | --- |
| `credential_id` | string | A `secret`-kind credential. |
| `raw` | boolean | `false` (default) returns a one-time-use `secret_token` for paybox-mediated egress; `true` returns the actual plaintext (only if the grant allows raw). |
| `purpose` | string? | Why the secret is needed — shown at approval, recorded in audit. |

**Returns** the [result envelope](#the-result-envelope). Prefer `raw: false` so
the plaintext never transits the model. Returns `pending_approval` when policy
requires the user's passkey.

***

## request\_swap

Swap one token for another from a **wallet** credential. You submit an
**intent** (what to swap); Paybox quotes the route via MoonX, builds the
transactions, the signing window signs them, and the server broadcasts.

| Field | Type | Notes |
| --- | --- | --- |
| `credential_id` | string | A `wallet`-kind credential. |
| `src_chain` | string | CAIP-2 source chain — an `eip155:*` chain for an EVM wallet, `solana:*` for Solana. |
| `dst_chain` | string? | Defaults to `src_chain`. Set a different chain to **bridge**: after the source tx broadcasts, `get_request` returns `pending_settlement` until the bridge delivers. |
| `src_token` | string | Token address, or `"native"` for the chain's native asset. |
| `dst_token` | string | Destination token address. |
| `amount` | string | Amount in `src_token`'s smallest unit, as a decimal string. |
| `swap_direction` | string? | `exact-amount-in` (default) or `exact-amount-out`. |
| `slippage_bps` | integer? | Default `50` (0.5%). Becomes the on-chain `amountOutMin` floor. |
| `recipient` | string? | Defaults to the wallet's own address. For a cross-VM swap (e.g. Solana → Base) pass an address on `dst_chain`; omitted, it defaults to the user's single vaulted wallet there. |
| `value_cents` | integer? | Rough USD value of the sell side, for policy. |

**Returns** the [result envelope](#the-result-envelope): `pending_signature`
(autonomous) or `pending_approval`. A bad quote (e.g. an unsupported token pair)
fails **here**, synchronously. On completion, `get_request` returns the swap
transaction hash in `output`; a cross-chain swap's `success` usually includes
the destination `dst_tx_hash` too.

:::tip
Call **[`get_portfolio`](#get_portfolio)** first to pick the token and size the
`amount` before you swap.
:::

***

## get\_portfolio

List a wallet's token balances across chains (public on-chain data via MoonX).

| Field | Type | Notes |
| --- | --- | --- |
| `address` | string | Wallet address (EVM `0x…` or Solana base58). |
| `network_ids` | string? | Comma-separated network IDs (e.g. `"1,8453,137"`); omit to auto-detect from the address. |

**Returns** the requested wallet `address`, `wallet_name` when it matches an
active owned wallet, each holding's token address, resolved `name` + `symbol`,
24h `priceChange24h` (a fraction, e.g. `0.0052` = +0.52%; `null` when unknown),
raw + USD balance, network id, freshness metadata, and `total_usd` (`null` if
any nonzero holding is unpriced).
When World is enabled, outcome `balanceUsd` is a number from the held side's
orderbook midpoint, or `null` with `price_unavailable_reason`.

***

## verify\_solana\_balance

Verify that one confirmed Solana transaction changed an exact wallet and SPL or
Token-2022 mint, then read the current token balance at or after that
transaction's slot. This read comes directly from the configured Alchemy Solana
Mainnet RPC and does not wait for MoonX's portfolio index.

| Field | Type | Notes |
| --- | --- | --- |
| `address` | string | Canonical base58 Solana wallet address. |
| `token_mint` | string | Canonical base58 SPL or Token-2022 mint. |
| `transaction_signature` | string | Canonical base58 64-byte transaction signature. |

**Returns** the transaction slot and exact pre/post/delta amounts, plus the
current raw balance, decimals, display string, and observation slot.
`read_covers_transaction: true` means the balance read was pinned to at least
the transaction slot. It does **not** mean MoonX or
[`get_portfolio`](#get_portfolio) has indexed the transaction.

This tool is read-only and requires authentication. It fails closed when the
Alchemy key is missing, the RPC remains behind the transaction slot, the
transaction failed, or the wallet/mint pair was not affected.

***

## get\_buy\_link

Generate a **signed MoonPay checkout URL** that buys crypto with fiat
(card/bank) straight into a **wallet** credential — the way to fund a wallet
that has no balance yet. The buyer opens the link in a browser and completes
the purchase on MoonPay's page (payment + KYC happen there); the purchased
crypto is delivered to the wallet's address, pre-filled from the credential.

| Field | Type | Notes |
| --- | --- | --- |
| `credential_id` | string | A `wallet`-kind credential from `list_credentials`. Its captured `address` becomes the purchase destination. |
| `destination_chain` | string? | Exact destination. Omit for Base (`eip155:8453`) on EVM wallets or Solana mainnet on Solana wallets. Ethereum mainnet requires explicit `eip155:1`. |
| `currency_code` | string? | MoonPay code enabled for this integration and bound by MoonPay's catalog to the exact destination. Omit for `usdc_base` on Base, `eth` on explicit Ethereum mainnet, or native SOL on Solana. Bare `usdc` is canonicalized to `usdc_base` only when the destination resolves to Base. |
| `amount_usd` | number? | Fiat USD amount to pre-fill (e.g. `50` = $50). Omit to let the buyer pick at checkout. |

**Returns** the finished link synchronously (no result envelope — there is
nothing to poll):

```json
{
  "url": "https://buy.moonpay.com?apiKey=…&currencyCode=usdc_sol&walletAddress=…&signature=…",
  "currency_code": "usdc_sol",
  "wallet_address": "5EUa…SViS",
  "network": "solana:mainnet",
  "message": "Share this MoonPay checkout url with the user…"
}
```

* The URL is **signed server-side** (HMAC over the query string), so the
  pre-filled destination can't be tampered with — MoonPay refuses to load an
  altered link. The signing key never leaves the server.
* Generating a link **moves no money and needs no approval** — it only points a
  purchase at the user's own wallet. The payment itself happens on MoonPay's
  page, by the human.
* A disabled code or a code that settles on a different exact network is
  rejected before signing. If USDC on Base is unavailable, no link is returned
  and the caller must not retry another network unless the user named it.
* On a deployment without buy-link support the tool returns an `error`.

:::tip
After the user reports the purchase complete, use
**[`verify_solana_balance`](#verify_solana_balance)** when the completed Solana
checkout gives you a transaction signature. Otherwise confirm arrival with
**[`get_portfolio`](#get_portfolio)**. Card purchases can take a few minutes to
settle on-chain.
:::

***

## discover\_services

Search the curated x402 Bazaar set for paid services. By default, Paybox returns
services with recent usage, at least 10 unique payers in the last 30 days, and a
supported x402 rail. This is read-only; it does not make a payment.

| Field | Type | Notes |
| --- | --- | --- |
| `query` | string? | Search query, e.g. `"weather api"`. Omit for the curated listing. |
| `raw` | boolean? | Debug escape hatch. `false` by default; `true` returns the uncurated Bazaar catalog/search response. |

**Returns** service summaries with resource URLs, descriptions, and accepted
payment requirements. Pick a service, then pay it with [`pay_x402`](#pay_x402)
or let Paybox fetch it with [`use_service`](#use_service).

Hidden on deployments where x402 is disabled.

***

## pay\_x402

Pay an x402 endpoint from a **wallet** credential and return the payment header
to the agent. Use this when the agent or client wants to retry the resource
itself.

| Field | Type | Notes |
| --- | --- | --- |
| `credential_id` | string | A wallet-kind credential from `list_credentials`. |
| `accepts` | array | The 402's `accepts` PaymentRequirements array, verbatim. |
| `resource_url` | string | The paid resource URL, used for audit and display. |
| `x402_version` | integer? | `1` for JSON-body requirements, `2` for `PAYMENT-REQUIRED` header requirements. Defaults to `1`. |
| `resource` | object? | x402 v2 resource block from `PAYMENT-REQUIRED`; omit for v1. |

**Returns** the [result envelope](#the-result-envelope). On success,
`output.value.x_payment` has the header name and value the agent can send to the
paid resource.

:::warning
x402 requires a plain EOA payer. If the wallet is still EIP-7702 delegated, the
tool either returns a plain-EOA error or — where auto-undelegate is enabled for
the chain — runs one composite signing flow that undelegates first, verifies the
wallet is plain, and only then produces the x402 payment.
:::

Hidden on deployments where x402 is disabled.

***

## use\_service

Fetch a paid x402 resource through Paybox gateway mode. Paybox probes the
resource, parses the 402, opens the signing window, then re-fetches the resource
with the payment header and stores the response in the request output.

| Field | Type | Notes |
| --- | --- | --- |
| `credential_id` | string | A wallet-kind credential from `list_credentials`. |
| `url` | string | Paid x402 resource URL. |
| `method` | string? | HTTP method. Defaults to `GET`. |
| `body` | object? | JSON body sent only on the paid re-fetch, never on the unpaid 402 probe. |
| `headers` | object? | Custom headers forwarded on the 402 probe and paid re-fetch. |

**Returns** the [result envelope](#the-result-envelope). On success,
`output.value.response` contains `{ status, content_type, body }`.

Use this when the agent just wants the paid content. Use
[`pay_x402`](#pay_x402) when the agent needs the payment header and will call the
resource itself.

Exposed only on deployments with x402 gateway mode enabled; check `tools/list`.

***

## get\_request

Poll a previously-issued request. This is how every `pending_*` operation
reaches its result — the agent never calls the internal completion tools.

| Field | Type | Notes |
| --- | --- | --- |
| `request_id` | string | From a prior `request_*` result. |

**Returns** the same [result envelope](#the-result-envelope) shape, reflecting
the latest state:

* `pending_approval` → still waiting on the user's passkey.
* `pending_signature` → approved/autonomous; the signing window is finishing it.
* `success` → done; `output` holds the artifact (signature, tx hash, token, …).
* `denied` → includes `reason`.

Only the client that minted a request can read it. Polling is idempotent.

***

## list\_requests

List this client's own recent requests, newest first — the agent's
request/trade history. Read-only: no funds move, nothing is signed, and it
returns **only requests this client created**, never another client's.

| Field | Type | Notes |
| --- | --- | --- |
| `status` | string? | Filter by status (snake\_case, e.g. `success`, `denied`, `pending_approval`). Omit for every status. |
| `limit` | integer? | Max rows, newest first. Default 200, capped at 500. |
| `before` | string? | Keyset cursor, first half: the last row's `created_at` (RFC 3339). Pair with `before_id`. |
| `before_id` | string? | Keyset cursor, second half: the last row's `request_id`. The pair forms a total cursor, so rows sharing a timestamp are never skipped. |

**Returns** rows carrying `request_id`, `kind` (`payment` | `wallet_sign` |
`secret` | `swap` | …), `status`, `created_at` + `completed_at`, a **redacted**
`output` (present only on `success`), `approval_id`, and `error`.

* Outputs are redacted exactly like `get_request` polls — to obtain the actual
  artifact of one request, call [`get_request`](#get_request) with its
  `request_id`.
* To page, pass **both** `before` and `before_id` from the last row of the
  previous page.
* `pending_*` rows reflect the last stored status and may lag a request's true
  terminal state; `get_request` gives the live answer.

***

## request\_account\_change

Ask the **user** to update this connector's access — grant another credential,
raise a limit, change an approval mode, or create a wallet/card/secret they
don't have yet. Call it when an operation is blocked by policy or a missing
grant.

| Field | Type | Notes |
| --- | --- | --- |
| `note` | string? | One short sentence describing what needs to change (e.g. `"needs an EVM wallet to sign"`). Shown verbatim to the user so they know why the window opened. |

**Returns** synchronously (nothing is parked — the agent only ever receives a
link, never the user's vault):

```json
{
  "kind": "account_change",
  "client_id": "…",
  "manage_url": "https://app.paybox.sh/clients/…/manage?note=…",
  "note": "needs an EVM wallet to sign"
}
```

Surface `manage_url` and ask the user to open it. They make the change in their
own passkey-gated Paybox session; nothing is applied on the agent's behalf.
Don't claim the change is done until the user confirms or the retried operation
succeeds.

***

## Plugin tools

Beyond the core tools above, Paybox has **first-party plugins** — batches of
extra tools (e.g. prediction-market or spot-market access) a user switches on
from the app's **Plugins** screen.

You don't need a separate reference for them — **discover them at runtime**:

* A plugin's tools appear in the MCP **`tools/list`** response (with full input
  schemas and descriptions) **only when the user has enabled that plugin**.
  List tools after connecting, and again when the user says they've changed
  their plugins.
* Calling a plugin tool the user hasn't enabled returns an error naming the
  plugin; point the user at the app's Plugins screen (or
  [`request_account_change`](#request_account_change)) and retry after they
  enable it.
* Plugin tools follow all the same rules as the core surface: the
  [result envelope](#the-result-envelope), the poll pattern, and passkey
  approvals — plugin **money tools always pause for the user's approval**, even
  under an autonomous grant.

***

## Errors

Calls to `/mcp` without a valid bearer token return **`401`** with a
`WWW-Authenticate` header pointing at discovery, so the client can re-authorize:

```http
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://api.paybox.sh/.well-known/oauth-protected-resource", error="invalid_token"
```

Beyond auth, problems surface in the result envelope: `denied` (a policy or user
decision, with a `reason`) versus `error` (an internal failure, with a
`message`). See **[OAuth 2.1 → Errors](/connect/oauth#errors)** for token-exchange
error codes.
