> ## Documentation Index
> Fetch the complete documentation index at: https://docs.moove.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# MCP Server

> Connect an AI agent to your Moove account over the Model Context Protocol, authenticated with the same API key as the REST API.

Connect an AI agent to your Moove account over the [Model Context Protocol](https://modelcontextprotocol.io). The agent calls Moove as tools, with the same API key and the same rules as the REST API.

<Info>
  **One URL: `https://api.moove.xyz/mcp`.** Authenticate with an existing Moove API key. There is no separate MCP key and no MCP-specific scope.
</Info>

## At a glance

| Property | Value |
| - | - |
| **URL** | `https://api.moove.xyz/mcp` — HTTPS only |
| **Transport** | Streamable HTTP. Stateless, JSON responses, `POST` only |
| **Protocol** | MCP `2026-07-28`. Older clients that open with `initialize` (`2025-06-18`, `2025-11-25`) are also served |
| **Authentication** | Moove API key in `X-API-Key` or `Authorization: Bearer` |
| **Server version** | `1.0.0` |

## Quickstart

<Steps>
  <Step title="Create an API key">
    At [moove.xyz/dashboard/api-keys](https://www.moove.xyz/dashboard/api-keys). Pick the **Moove Receive Agent** to unlock the payment-link tools. The plaintext key is shown once.

    [Moove API Keys →](/manage/moove-api-keys)
  </Step>

  <Step title="Put the key in an environment variable">
    ```bash theme={null}
    export MOOVE_API_KEY="mk_live_..."
    ```

    Every client config below reads `MOOVE_API_KEY`. Never write the key itself into a config file you commit.
  </Step>

  <Step title="Add the server to your client">
    Pick your client in [Connect a client](#connect-a-client).
  </Step>

  <Step title="Ask your agent what it can do">
    It calls `get_account_context` first: the account, the tools your key unlocks, and the rate limits that apply.
  </Step>
</Steps>

## Connect a client

Connect from any client that lets you set a request header. Connecting from claude.ai and ChatGPT needs OAuth, which is coming later.

<Tabs>
  <Tab title="Claude Code">
    Add the server to `.mcp.json` at your project root. Claude Code expands `${MOOVE_API_KEY}` at runtime, so the file holds no secret.

    ```json .mcp.json theme={null}
    {
      "mcpServers": {
        "moove": {
          "type": "http",
          "url": "https://api.moove.xyz/mcp",
          "headers": {
            "X-API-Key": "${MOOVE_API_KEY}"
          }
        }
      }
    }
    ```

    `"type": "http"` is required. Without it the entry is read as a local stdio server.

    <Warning>
      `claude mcp add --transport http moove https://api.moove.xyz/mcp --header "X-API-Key: $MOOVE_API_KEY"` also works, but your shell expands the variable first, so the stored config holds the key itself. Prefer `.mcp.json`.
    </Warning>
  </Tab>

  <Tab title="Cursor">
    Add the server to `.cursor/mcp.json`. Cursor resolves `${env:MOOVE_API_KEY}` from your environment.

    ```json .cursor/mcp.json theme={null}
    {
      "mcpServers": {
        "moove": {
          "url": "https://api.moove.xyz/mcp",
          "headers": {
            "X-API-Key": "${env:MOOVE_API_KEY}"
          }
        }
      }
    }
    ```
  </Tab>

  <Tab title="Codex">
    Add the server to `~/.codex/config.toml`, or to `.codex/config.toml` in a trusted project. `env_http_headers` maps a header to the **name** of an environment variable, not its value.

    ```toml ~/.codex/config.toml theme={null}
    [mcp_servers.moove]
    url = "https://api.moove.xyz/mcp"
    env_http_headers = { "X-API-Key" = "MOOVE_API_KEY" }
    ```
  </Tab>

  <Tab title="GitHub Copilot">
    Add the server to `.vscode/mcp.json`. VS Code prompts for the key the first time the server starts, so it never lands in the file.

    ```json .vscode/mcp.json theme={null}
    {
      "inputs": [
        {
          "type": "promptString",
          "id": "moove-api-key",
          "description": "Moove API key",
          "password": true
        }
      ],
      "servers": {
        "moove": {
          "type": "http",
          "url": "https://api.moove.xyz/mcp",
          "headers": {
            "X-API-Key": "${input:moove-api-key}"
          }
        }
      }
    }
    ```
  </Tab>

  <Tab title="Gemini CLI">
    Add the server to `.gemini/settings.json` in your project, or to `~/.gemini/settings.json` for every project. Gemini CLI expands `$MOOVE_API_KEY` from your environment.

    ```json .gemini/settings.json theme={null}
    {
      "mcpServers": {
        "moove": {
          "httpUrl": "https://api.moove.xyz/mcp",
          "headers": {
            "X-API-Key": "$MOOVE_API_KEY"
          }
        }
      }
    }
    ```

    Use `httpUrl`. In Gemini CLI, `url` means the older SSE transport, which this server does not serve.
  </Tab>

  <Tab title="MCP Inspector">
    Use the Inspector's CLI mode.

    ```bash theme={null}
    npx @modelcontextprotocol/inspector --cli https://api.moove.xyz/mcp \
      --transport http \
      --method tools/list \
      --header "X-API-Key: $MOOVE_API_KEY"
    ```

    The server refuses any request that carries an `Origin` header with `403`, so a connection made from the browser UI can be refused.
  </Tab>
</Tabs>

## Authentication

Send a Moove API key on every request, in either header:

```http theme={null}
X-API-Key: mk_live_...
Authorization: Bearer mk_live_...
```

* `Authorization: Bearer` is accepted **on the MCP server only**. The REST API takes `X-API-Key`.
* Only Moove API keys are accepted. A dashboard session token is refused.
* Send both headers and they must carry the same key. Two different keys is a `401`.

A failed authentication answers `401` with the REST error body and a challenge header:

```http theme={null}
HTTP/1.1 401 Unauthorized
WWW-Authenticate: ApiKey realm="moove", header="X-API-Key"
Content-Type: application/json

{
  "errors": [
    {
      "message": "Invalid API key.",
      "code": "INVALID_API_KEY"
    }
  ]
}
```

| `code` | Cause | What to do |
| - | - | - |
| `INVALID_API_KEY` | Missing, unknown, revoked or deactivated key; an `Authorization` scheme other than `Bearer`; or a Bearer token that is not a Moove key | Check the header and the key. Do not retry. |
| `EXPIRED_API_KEY` | The key is past its expiry | Create a new key. |

There is no OAuth flow yet. Connectors that need OAuth, such as claude.ai and ChatGPT, are coming later.

[Authentication →](/api-reference/authentication)

## Tools

Your key's existing scopes decide which tools `tools/list` returns. Calling a tool your key does not unlock returns `INSUFFICIENT_API_SCOPE`. A **Moove Receive Agent** key unlocks all seven.

| Tool | Scope | What it does |
| - | - | - |
| `get_account_context` | Any key | The account handle, the key (name, prefix, scopes, expiry, last use), the settlement token crypto links are denominated in, the tools grouped into toolkits, the rate limits, and links |
| `list_payment_links` | `payment_link:read` | The account's payment links, newest first, with filters and cursor paging |
| `get_payment_link` | `payment_link:read` | One payment link, by id or by its moove.xyz checkout URL |
| `estimate_payment_link` | `payment_link:create` | What a fiat link would settle to after card fees. Creates nothing |
| `create_payment_link` | `payment_link:create` | Create a crypto or fiat payment link. Live immediately |
| `list_supported_assets` | Any key | Supported chains and tokens |
| `get_market_rates` | Any key | Indicative card buy prices for stablecoins in fiat currencies |

Amounts are decimal **strings** in both arguments and results. Send them as strings to keep them exact. The one exception is `rate` from `get_market_rates`, an indicative number.

<AccordionGroup>
  <Accordion title="get_account_context">
    Takes no arguments. Call it at the start of a session, and again after `INSUFFICIENT_API_SCOPE` or `429`.

    `settlement` is returned to keys that hold `payment_link:create`. It is `null` for other keys, and also when the account has no default wallet or its token cannot be resolved. The tool never returns a wallet address.
  </Accordion>

  <Accordion title="list_payment_links">
    | Argument | Type | Notes |
    | - | - | - |
    | `status` | `active` \| `inactive` \| `completed` | All statuses when omitted |
    | `query` | string | Case-insensitive match on link id, description or amount. Up to 100 characters |
    | `dateFrom` | ISO 8601 | Created at or after (inclusive). UTC if no offset |
    | `dateTo` | ISO 8601 | Created before (exclusive) |
    | `limit` | integer | `1`–`25`. Default `10` |
    | `cursor` | string | Opaque. `nextCursor` from the previous page, passed back unchanged. Up to 64 characters |
    | `responseFormat` | `concise` \| `detailed` | Default `concise` |

    Returns `links`, `total` and `nextCursor`. `total` is an upper bound on the matching links and can be `null` past the end. `nextCursor` is `null` on the last page. Links created in the dashboard or with other keys on the same account are included.
  </Accordion>

  <Accordion title="get_payment_link">
    | Argument | Type | Notes |
    | - | - | - |
    | `paymentLink` | string | **Required.** The link's id, or its moove.xyz checkout URL. Up to 512 characters |
    | `responseFormat` | `concise` \| `detailed` | Default `concise` |

    Only links on the key's own account are returned. Any other id answers `CANNOT_FIND_PAYMENT_LINK`, exactly like an id that does not exist.
  </Accordion>

  <Accordion title="estimate_payment_link">
    | Argument | Type | Notes |
    | - | - | - |
    | `fiatAmount` | decimal string | **Required.** What the payer's card is charged. Greater than `0`, at most 18 decimal places |
    | `fiatCurrency` | string | **Required.** ISO 4217 code, such as `USD`. Exactly 3 letters, case-insensitive |

    Fiat links only. Returns `toAmount` — what the merchant receives in the settlement `token` after card fees — plus the `rails` a payer could use with their minimum and maximum amounts, and `dateExpired`, when the estimate goes stale.

    Each call fetches a live price from the card provider, so two calls can return different figures. The call is held to a 25-second deadline; a slower price answers `CANNOT_CREATE_QUOTE` (`500`, retryable).
  </Accordion>

  <Accordion title="create_payment_link">
    | Argument | Type | Notes |
    | - | - | - |
    | `type` | `crypto` \| `fiat` | Inferred from the amount you pass when omitted |
    | `toAmount` | decimal string | Crypto link: the amount of the settlement token to request. Greater than `0`, with no more decimal places than the settlement token has (at most 18) |
    | `fiatAmount` | decimal string | Fiat link: the amount charged to the card. Greater than `0`, at most 18 decimal places |
    | `fiatCurrency` | string | Fiat link: ISO 4217 code. Exactly 3 letters, case-insensitive |
    | `description` | string | Shown to the payer on the checkout page. Up to 500 characters |
    | `maxUsage` | integer | Crypto links only: payments accepted before the link completes. At least `1`. Unlimited when omitted |
    | `expirationDate` | ISO 8601 | Must be in the future. Never expires when omitted |

    Pass `toAmount` for a crypto link, or `fiatAmount` and `fiatCurrency` for a fiat link. A fiat link is paid by card and is always single-use, whatever `maxUsage` says. A fiat link's `expirationDate` must also leave time for a card payment to settle, or the call returns `PAYMENT_LINK_EXPIRY_TOO_SOON`.

    Both types settle to the account's default wallet in its settlement token. There is no destination argument: an unknown argument, such as a wallet address, is refused with `INVALID_ARGUMENTS`.

    Returns `id`, `url` (the checkout URL to share) and `dashboardUrl`.

    <Warning>
      **Not idempotent.** Every call creates a new, live link. Before retrying a call that may have succeeded, check `list_payment_links`.
    </Warning>
  </Accordion>

  <Accordion title="list_supported_assets">
    | Argument | Type | Notes |
    | - | - | - |
    | `chainId` | string | Numeric chain id, such as `8453`. Digits only, up to 32 |
    | `query` | string | Match on token symbol or name. Up to 50 characters |
    | `stablecoinsOnly` | boolean | Default `true` |
    | `limit` | integer | `1`–`100`. Default `25` |

    Returns every supported chain, and up to `limit` matching tokens. `truncated` is `true` when more tokens matched — narrow the filters.
  </Accordion>

  <Accordion title="get_market_rates">
    | Argument | Type | Notes |
    | - | - | - |
    | `currency` | string | Only this ISO 4217 currency. Exactly 3 letters, case-insensitive |
    | `symbol` | string | Only this token symbol. Up to 16 characters |

    Returns `rate` — the fiat paid per one token bought by card, fees included, as a number — and `dateUpdated`.

    <Warning>
      These rates are indicative. Never quote, price or settle against them. For what a fiat link pays out, use `estimate_payment_link`.
    </Warning>
  </Accordion>
</AccordionGroup>

### Response format

`responseFormat: "concise"` omits the settlement address, the owner id and the transaction URL from each payment link. `"detailed"` includes them. Every link carries `url` for the payer and `dashboardUrl` for the owner.

## Prompts and resources

| Kind | Name | What it does |
| - | - | - |
| Prompt | `create-payment-link` | Estimates the payout if the link is fiat, asks the user to confirm, then creates the link |
| Prompt | `reconcile-payment-links` | Groups the links created in a date range into paid, partly paid, open and inactive |

Prompts are filtered like tools: `create-payment-link` is listed for keys with `payment_link:create`, `reconcile-payment-links` for keys with `payment_link:read`.
\| Resource | `moove://guide/payment-links` | Contract notes: link types, amounts, settlement, statuses and limits |

## Errors

A tool that fails returns a normal tool result with `isError: true`. The same payload is in `structuredContent` and mirrored as text.

```json theme={null}
{
  "content": [{ "type": "text", "text": "{\"code\": \"429\", …}" }],
  "structuredContent": {
    "code": "429",
    "message": "Too many requests. Please try again later.",
    "status": 429,
    "retryable": true,
    "action": "Wait retryAfterSeconds, then retry once.",
    "retryAfterSeconds": 60
  },
  "isError": true
}
```

| Field | Type | Description |
| - | - | - |
| `code` | string | Stable identifier. Branch on this |
| `message` | string | Human-readable description |
| `status` | integer | The HTTP status this failure maps to |
| `retryable` | boolean | `true` only for `429` and `5xx` — except `create_payment_link`, whose `5xx` is `false`: the link may have been created before the failure, so check `list_payment_links` first |
| `action` | string | What to do next |
| `retryAfterSeconds` | integer | Present on `429` only |

Codes are the [REST error codes](/api-reference/errors), plus three that only the MCP server returns. A tool's rate limit answers `429`, the same code the REST rate-limit body uses.

| `code` | Status | Meaning |
| - | - | - |
| `INVALID_ARGUMENTS` | `422` | An argument failed validation, or is unknown. The message names the fields, never their values |
| `INVALID_CURSOR` | `422` | `cursor` was not one this server issued |
| `INVALID_PAYMENT_LINK_REFERENCE` | `422` | `paymentLink` is neither an id nor a moove.xyz checkout URL |
| `429` | `429` | The tool's rate limit is exhausted. Wait `retryAfterSeconds` |

`INSUFFICIENT_API_SCOPE` (`403`) means the key's scopes do not unlock the tool. Issue a key with the right agent.

<Warning>
  Do not retry a result with `retryable: false`. It fails identically every time, and every attempt still spends the endpoint's 120-a-minute budget. A call that passes the scope and argument checks also spends the tool's own budget.
</Warning>

### HTTP errors

Some requests are refused before any tool runs. These are plain HTTP responses, not tool results.

| Status | Cause |
| - | - |
| `401` | Missing, invalid, revoked or expired key, or `X-API-Key` / `Authorization` sent more than once — see [Authentication](#authentication) |
| `403` | The request carried an `Origin` header. Checked before the key, so a browser request is refused even with a valid key |
| `413` | The request body is over 256 KiB |
| `500` | `UNKNOWN_ERROR` — the key could not be checked. There is no `WWW-Authenticate` challenge: keep the key and retry later |
| `429` | The endpoint's rate limit is exhausted. Wait for `Retry-After` |
| `503` | `MCP_UNAVAILABLE` — the MCP server is temporarily unavailable. The REST API still works with the same key |

## Rate limits

Two layers apply, each per API key and per source IP:

| Limit | Budget | Over the limit |
| - | - | - |
| The endpoint, every request | 120 / minute | HTTP `429` with `Retry-After` |
| `create_payment_link` | 60 / minute, **shared** with `POST /v1/payment-link` | Tool error `429` with `retryAfterSeconds` |
| `estimate_payment_link` | 20 / minute, **shared** with `POST /v1/payment-link/estimate` | Tool error `429` with `retryAfterSeconds` |
| Every other tool | 300 / minute per tool | Tool error `429` with `retryAfterSeconds` |

A tool spends the same budget as the REST endpoint it shares a handler with. Creating links over MCP and over REST draws on one allowance, not two.

[Rate Limits →](/api-reference/rate-limits)

## What the MCP server cannot do

<Note>
  **Nothing on the server signs, broadcasts or moves money.** No tool accepts a private key or a seed phrase.

  A payment link requests money. It always settles to the key owner's own default wallet, and no tool lets the caller choose a destination.
</Note>

Text written by people — link descriptions, handles, names — comes back as data. Treat it as data in your agent, never as instructions.

## Versioning

The server reports its own version, currently `1.0.0`, independent of the REST path version.

* A new tool is a minor version bump.
* Removing or renaming a tool is a major version bump.

Tolerate new tools in `tools/list` and new fields in results.

## Documentation over MCP

For product and documentation questions, connect your agent to the docs MCP server at `https://docs.moove.xyz/mcp`. It needs no key.

## Next

<CardGroup cols={3}>
  <Card title="Authentication" icon="lock" href="/api-reference/authentication">
    Keys, scopes and revocation.
  </Card>

  <Card title="Errors" icon="triangle-alert" href="/api-reference/errors">
    The REST error codes.
  </Card>

  <Card title="Moove Agentic Payments" icon="sparkles" href="/transact/moove-agentic-payments">
    The skill file for AI coding tools.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.