---
title: "Gravv MCP server"
description: "Connect an AI assistant to Gravv with the @gravv-infra/mcp server: install it, pick toolsets, and know the confirm, live-money and sandbox-only gates."
---
The Gravv MCP server connects an AI assistant to the Gravv API through the [Model Context Protocol](https://modelcontextprotocol.io). The assistant can search these docs, then onboard customers, run KYC, open accounts, add payees, move money, issue cards and exchange currency with your own API key. It runs on your machine as a stdio process, so it works with Claude, Cursor, VS Code and any other MCP client.

This page describes version **0.4.0** of the npm package `@gravv-infra/mcp`. Gravv published earlier releases as `@gravvfi/mcp`. Install `@gravv-infra/mcp`.

**Example.** You ask your assistant to "pay our US supplier 1,250 USD by ACH". It searches the docs and learns that a new payee must reach `active` before a transfer can succeed. It adds the payee with `createExternalAccount`, reads it with `getExternalAccount` until it is `active`, and calls `createTransfer`. That first call does not run. It returns a preview: "Transfer 1250.00 USD from … to external_account … Once submitted this cannot be reversed from the API." The transfer runs only when you agree and the assistant calls again with `confirm: true`.

## Before you start

- Node.js 20 or later (`node --version`).
- A **sandbox** API key that starts with `grvSec_sandbox_`. See [Authentication](/getting-started/authentication). The key decides the environment, because sandbox and live share one host, `https://api.gravv.xyz`.
- Keep the key out of files you commit. Where your client can read it from an environment variable or ask for it, use that instead of pasting it into a config file.

Without a key, the server still starts in [docs-only mode](#documentation-tools-and-docs-only-mode).

To run the same calls yourself from a terminal, use the [Gravv CLI](/tools/gravv-cli). It uses the same error fixes and the same list of calls that move money as this server.

## Install

Every client runs the same command: `npx -y @gravv-infra/mcp`, with `GRAVV_API_KEY` in its environment. Replace `<SANDBOX_API_KEY>` with your key.

### Claude Code

```bash title="Add the server"
claude mcp add gravv --env GRAVV_API_KEY=<SANDBOX_API_KEY> -- npx -y @gravv-infra/mcp
```

### VS Code and GitHub Copilot

VS Code uses `servers`, not `mcpServers`, and needs `type`. Put this in `.vscode/mcp.json` to share it with your team, or run **MCP: Open User Configuration** for your own:

```json title=".vscode/mcp.json"
{
  "servers": {
    "gravv": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "@gravv-infra/mcp"],
      "env": { "GRAVV_API_KEY": "<SANDBOX_API_KEY>" }
    }
  }
}
```

### Codex CLI

```bash title="Add the server"
codex mcp add gravv --env GRAVV_API_KEY=<SANDBOX_API_KEY> -- npx -y @gravv-infra/mcp
```

Or in `~/.codex/config.toml`:

```toml title="config.toml"
[mcp_servers.gravv]
command = "npx"
args = ["-y", "@gravv-infra/mcp"]

[mcp_servers.gravv.env]
GRAVV_API_KEY = "<SANDBOX_API_KEY>"
```

### Claude Desktop, Cursor, Windsurf, Cline and Zed

These use the `mcpServers` shape:

```json title="mcpServers"
{
  "mcpServers": {
    "gravv": {
      "command": "npx",
      "args": ["-y", "@gravv-infra/mcp"],
      "env": { "GRAVV_API_KEY": "<SANDBOX_API_KEY>" }
    }
  }
}
```

| Client | Where the configuration goes |
| :-- | :-- |
| Claude Desktop | `claude_desktop_config.json` (open Settings, then Developer, then Edit Config) |
| Cursor | `~/.cursor/mcp.json`, or `.cursor/mcp.json` in a project |
| Windsurf | `~/.codeium/windsurf/mcp_config.json` |
| Cline | The MCP Servers panel, or `cline_mcp_settings.json` |
| Zed | `settings.json`, under `context_servers` |

### Any other client

Point it at a stdio process:

```text title="Any MCP client"
command: npx
args:    ["-y", "@gravv-infra/mcp"]
env:     GRAVV_API_KEY=<SANDBOX_API_KEY>
```

The server negotiates MCP protocol version `2025-06-18` and still serves clients that speak only `2024-11-05`.

### Check it works

Ask your assistant "What Gravv accounts do I have?". It should call `listAccountsV2` and answer with your sandbox data. If you have no accounts yet, ask it to search the Gravv docs for how to open one. The server also writes one line to stderr when it starts, with the number of tools and prompts and the environment.

## How to use it

Ask in plain words, as you would ask a teammate. The assistant picks the tools, reads the docs it needs and shows you what it did.

| You ask | What the assistant does |
| :-- | :-- |
| "How do I pay a Kenyan mobile money wallet?" | Searches these docs with `searchGravvDocs` and answers with the steps and links |
| "Onboard a test customer called Ada Lovelace" | Calls `createCustomer`, then `startCustomerKyc`, and gives you the KYC link |
| "Which accounts can a Nigerian business open?" | Calls `getAccountCapabilities` with `NG` and `business` |
| "Send 50 USD to payee ext_123" | Checks the payee is `active`, then shows a preview from `createTransfer`. It sends only after you agree |
| "Why is transfer trx_123 stuck?" | Reads the transaction and its webhooks, then tells you the next step |
| "Write a Python function that creates a payee" | Reads the payee docs and writes the code with the right fields |

For a whole task, pick one of the [prompts](#prompts) instead. A prompt tells the assistant what to ask you first and which tools to call in order.

To keep an assistant from changing anything, start the server with `--read-only`. It can then only read.

## Configuration

| Variable | Default | What it does |
| :-- | :-- | :-- |
| `GRAVV_API_KEY` | none | Your secret key. Its prefix selects the environment. Leave it out for docs-only mode |
| `GRAVV_ALLOW_LIVE_WRITES` | not set | `true` lets money-moving tools run on a live key. See [A live key needs a second signal](#a-live-key-needs-a-second-signal) |
| `GRAVV_TOOLSETS` | the default set | A comma-separated list of toolsets, or `all`. Same as `--toolsets` |
| `GRAVV_RATE_PER_MINUTE` | `60` | The server's own request rate, with bursts of 15. The API allows 600 a minute per business ([Rate limits](/getting-started/rate-limits)) |
| `GRAVV_BASE_URL` | `https://api.gravv.xyz` | The API host. Another host, or plain `http`, starts the server with a warning, because the server sends your key there. Same as `--base-url` |
| `GRAVV_DOCS_ORIGIN` | `https://gravv-docs.syntext.dev` | Where the documentation tools read these docs. Same as `--docs-origin` |
| `GRAVV_DOWNLOAD_DIR` | `gravv-mcp-downloads` in the system temp folder | Where the server saves downloaded files. Same as `--download-dir` |

The command-line options are `--toolsets=<list>`, `--read-only`, `--base-url=<url>`, `--docs-origin=<url>`, `--download-dir=<dir>` and `--help`. `--read-only` has no variable.

- A toolset name that does not exist stops the server with the list of names.
- Version 0.3.2 removed `approvals` and `payment-links`, and version 0.4.0 removed `account-applications`. The server ignores these names and gives a warning.
- Version 0.4.0 merged `features` into `cards`. For `features`, the server loads `cards` instead and gives a warning.
- A `--docs-origin` that is not an `http` or `https` address, or that carries a query, a fragment or credentials, stops the server with the reason.
- The server reads the key from the environment and sends it only to the API host. The server never writes it to disk, logs it or puts it in a result.

## Toolsets

There are 83 API tools in 12 toolsets. Every toolset loads by default. That is 83 tools, or 82 on a live key, because a live key hides the sandbox tool. The two documentation tools are always loaded.

```bash title="Choose toolsets"
npx -y @gravv-infra/mcp --toolsets=customers,accounts,cards
npx -y @gravv-infra/mcp --toolsets=all
```

| Toolset | Default | Tools |
| :-- | :-- | :-- |
| `customers` | Yes | `createCustomer`, `getCustomer`, `listCustomers`, `updateCustomer` |
| `kyc` | Yes | `startCustomerKyc`, `startCustomerKycS2S`, `uploadCustomerKycDocument`, `getCustomerKycDocuments`, `downloadCustomerKycDocument`, `getCustomerKycStatus` |
| `accounts` | Yes | `getAccountCapabilities`, `getAccountRequirements`, `createAccountV2`, `getAccountV2`, `listAccountsV2`, `getAccountApplicationV2`, `listAccountApplicationsV2`, `updateAccountApplicationV2`, and `createAccount`, `getAccount`, `listAccounts` for your business's one card account only |
| `external-accounts` | Yes | `createExternalAccount`, `getExternalAccount`, `listExternalAccounts`, `updateExternalAccount`, `verifyExternalAccount`, `listExternalAccountInstitutions` |
| `transfers` | Yes | `createTransfer`, `uploadTransferDocument`, `resumeTransfer`, `getCustomerLimits`, `getRemitterLimits`, `getTransferRates`, `listTransferSupportedCurrencies`, `listTransferSupportedCountries`, `listTransferSupportedCountriesForAddress` |
| `transactions` | Yes | `listTransactions`, `getTransaction`, `getTransactionsVolume`, `exportTransactions` |
| `cards` | Yes | `createCard`, `getCard`, `listCards`, `getCardBalance`, `updateCardStatus`, `withdrawFromCard`, `createCardApplication`, `getCardApplication`, `listCardApplications`, `startCardholderKyc`, `startCardholderKycS2S`, `uploadCardholderKycDocument`, `listFeatures`, `checkFeatureEligibility`, `activateFeature` |
| `wallets` | Yes | `createWallet`, `getWallet`, `listWallets` |
| `fx` | Yes | `getFxQuote`, `listFxRates`, `listFxCurrencyPairs`, `createFxOrder`, `getFxOrder`, `listFxOrders`, `cancelFxOrder`, `listFxPendingApprovals`, `listSettlementInstructions`, `createSettlementInstruction`, `getSettlementInstruction`, `updateSettlementInstruction`, `deleteSettlementInstruction` |
| `collections` | Yes | `createCardPaymentIntent`, `chargeSavedCard`, `listSavedCards`, `getSavedCard`, `deleteSavedCard`, `createCollection`, `getCollection` |
| `webhooks` | Yes | `getWebhookHistory`, `getWebhookEventDetail`, `getWebhookCallHistory`, `retryWebhookEvent` |
| `sandbox` | Yes, sandbox keys only | `simulateDeposit` |

- Card KYC (`startCardholderKyc` and its pair) verifies a cardholder for card issuing. It is separate from a customer's own KYC in `kyc`. After card KYC, `activateFeature` with `feature_id` `virtual_cards` issues a card at once in sandbox. In live it submits a card application, and you issue the card with `createCard` once Gravv approves it. See [Cards](/platform/cards/overview). If the assistant calls an old name, `startRiskKyc`, `startRiskKycS2S` or `uploadRiskDocument`, the server tells it the new name to use.
- `createCollection` takes bank transfers only. A card payment goes through `createCardPaymentIntent`, which returns the hosted payment link, the intent id and the card token that later charges need.
- Each tool's description lists its prerequisites, such as "the customer's KYC must be completed", and the assistant reads them before it calls.

## Safety gates

Sandbox and live share one host, so nothing in a request shows which environment it reaches. The server uses the gates below to keep the two apart.

### Money-moving tools take two calls

`createTransfer`, `resumeTransfer`, `withdrawFromCard`, `createFxOrder`, `cancelFxOrder`, `createCollection`, `createCardPaymentIntent` and `chargeSavedCard` return a preview on the first call. The preview has `status` `confirmation_required`, says what the call will do, and shows the request it will send. Nothing reaches the API. The call runs only when the assistant calls again with the same arguments plus `confirm: true`, after you agree.

- `resumeTransfer` is gated because resuming a [paused transfer](/platform/transfers/paused-transfers) releases the payout.
- `cancelFxOrder` is gated because you cannot undo a cancel, and Gravv does not return money it already debited.
- `createCardPaymentIntent` is gated because, with a saved `card_token`, it charges the card at once. It asks even when it only makes a link.

### A live key needs a second signal

On a `grvSec_live_` key, the server refuses money-moving tools, even with `confirm: true`, unless you started it with `GRAVV_ALLOW_LIVE_WRITES=true`. The server treats a key with any other prefix as live. Reads and other writes work on a live key without it. On a live key the preview also carries a warning that confirming moves real money.

### Tools that cannot be undone take two calls too

`deleteSavedCard`, `deleteSettlementInstruction` and `activateFeature` return a preview first and run only with `confirm: true`. `activateFeature` can issue a card, and you cannot switch off a feature it activates. These tools move no money, so they do not need `GRAVV_ALLOW_LIVE_WRITES`.

### The deposit simulator works only in sandbox

`simulateDeposit` ([`POST /v2/accounts/simulate/deposit`](/api-reference/accounts-v2/post-v2-accounts-simulate-deposit)) appears only with a key that starts `grvSec_sandbox_`. On any other key, the server hides it and refuses a call to it by name before it sends any request. The server does not offer the card and collection simulators.

### Read-only mode

`--read-only` turns off every tool that is not a GET, for reporting. For live, a safe setup is one read-only server entry on the live key, and all writes on a sandbox key.

### Files stay out of the conversation

Upload tools take a local `file_path`. The server reads the file, checks its type from its first bytes (JPEG, PNG or PDF) and its size, and sends it, so the file never passes through the assistant. The server refuses a file over the tool's limit before it sends anything. The limits are:

| Tool | Largest file |
| :-- | :-- |
| `uploadCustomerKycDocument` | 10 MB |
| `uploadCardholderKycDocument` | 5 MB |
| `uploadTransferDocument` | 700 KB |
| `createAccountV2`, `updateAccountApplicationV2` | 2 MB each |

In sandbox, keep the files of one call under 500 KB in total. See [Upload sizes](/getting-started/formats#upload-sizes). `downloadCustomerKycDocument` saves the image to a file readable only by you, in a folder only you can open. It returns the file's path, type and size, not the image.

### Cardholder data is never returned

The card number, CVV and PIN routes are not tools under any setting, and the server redacts any field named like card data from results. Show card details with the [client-side decryption flow](/platform/cards/view-card-sensitive-details/overview).

## Prompts

A prompt is a complete task that you pick in your client, often from `/` or a prompt menu. Each prompt does these things:

- It says what the assistant must ask you before any call.
- It gives the tool order for each path, with its confirm steps and how to recover.
- It names the docs page to read.
- It ends with the rules every task shares. Never invent a value, confirm before money moves, and know when to resend with the same `idempotency_key`.

An optional argument narrows a prompt to one path.

| Prompt | For | Optional argument |
| :-- | :-- | :-- |
| `open-account` | A bank account or your business's card account; an application the bank is waiting on; deposit details | `path`: `v2`, `card-account`, `manage` |
| `onboard-customer` | Create and verify a person or a business; resubmit; read KYC documents | `path`: `person-hosted`, `person-s2s`, `business`, `resubmit`, `documents` |
| `send-payout` | ACH, wire, RTP, SWIFT, SEPA, Faster Payments, or between own accounts | `rail`: `ach`, `wire`, `rtp`, `swift`, `sepa`, `fps`, `book` |
| `send-remittance` | A bank account or mobile wallet abroad | `country`: `NG`, `GH`, `KE`, `RW`, `ZM`, `UG`, `CG`, `CI`, `BJ`, `MX`, `BR`, `CO`, `IN`, `HK`, or `xof` |
| `send-crypto` | USDC or USDT to a wallet; USDT to USDC between own wallets | `path`: `same-network`, `cross-network`, `convert` |
| `convert-currency` | Quote and place an FX order; settlement instructions; follow or cancel | `path`: `quote-and-order`, `settlement-instruction`, `manage` |
| `issue-card` | Verify a cardholder and issue a card; fund, limit, freeze, close | `path`: `hosted-kyc`, `own-kyc`, `fund`, `limit`, `freeze-or-block`, `close`, `another-card` |
| `collect-payment` | Stablecoin deposits, bank deposits, bank transfers, card checkout, saved cards | `method`: `stablecoin`, `bank-deposit`, `bank-transfer`, `card`, `saved-card` |
| `reconcile` | Transactions against your books, one transfer's webhooks, fees, missed events | `path`: `transactions`, `trace`, `fees`, `webhooks` |
| `fix-stuck-transfer` | A transfer that is pending, paused, under review or failed | `transaction_id` |

Prompts use the same route labels as these docs (Available, Not yet available, Not supported). A prompt says when it needs a tool the session has not loaded, such as `simulateDeposit` on a live key, and when there is no API key.

## Documentation tools and docs-only mode

Two tools search and read every page of these docs:

- `searchGravvDocs` takes a `query`, an optional `limit` (default 6) and an optional docs `section`.
- `getGravvDocPage` takes a page slug or its full URL.

Both tools read the site's `/llms.txt` and `/llms-full.txt` and refresh them every hour. When they cannot reach the site, they keep answering from the last copy and add a note. Search matches words, not meaning. If it finds nothing, use the words these docs use, such as "transfer" or "payee".

Without `GRAVV_API_KEY`, the server runs in **docs-only** mode. The two documentation tools and the ten prompts work, and an API tool replies that it needs a key. You can use this mode to learn the API before you have credentials.

## Results and errors

- Every result carries `environment`, which is `sandbox`, `live` or `unknown`. `unknown` means the server does not recognize the key prefix, and the server treats that key as live. A write also carries the `idempotency_key` that the server sent.
- A result that is HTTP 200 but not done, such as a paused transfer, a declined card payment or a refused card KYC file, carries `next_step` ahead of `data`.
- An error carries `status`, the API's `code` when there is one, and a `hint` that says what to do instead.
- After a timeout, a network error or a `5xx`, the result says `outcome_unknown: true`, which means the call may have run. First check whether it ran, with the tool the hint names. If nothing happened, call again with the same arguments plus that `idempotency_key`. After any other `4xx`, call again without it, because the API keeps a `4xx` response for 24 hours under its key. See [Idempotent requests](/getting-started/idempotent-requests).
- `ignored_arguments` names the arguments the tool does not take. The server did not send them, so a misspelled filter shows up here.
- The server cuts a result over 100,000 characters. A list keeps fewer items and says to ask for a smaller page.

## Going live

1. Run the whole flow in sandbox first. Sandbox and live hold separate data, so an id from one does not exist in the other.
2. Replace `GRAVV_API_KEY` with your live key. The host does not change.
3. Reads and other writes work at once. Money movement stays blocked until you also set `GRAVV_ALLOW_LIVE_WRITES=true`:

```json title="Live, with money movement"
{
  "mcpServers": {
    "gravv": {
      "command": "npx",
      "args": ["-y", "@gravv-infra/mcp"],
      "env": {
        "GRAVV_API_KEY": "<LIVE_API_KEY>",
        "GRAVV_ALLOW_LIVE_WRITES": "true"
      }
    }
  }
}
```

## What it cannot do

| Asked for | Answer | Instead |
| :-- | :-- | :-- |
| Card number, CVV or PIN | Not available over MCP, under any setting | The [client-side decryption flow](/platform/cards/view-card-sensitive-details/overview) |
| Approving or rejecting a transfer, payee or FX order | Not available. Those routes are Dashboard-only | The Gravv Dashboard. FX orders placed with an API key need no approval |
| Card or collection simulators | Not offered yet | `simulateDeposit` funds a sandbox account |
| Payment links | Retired | `createCardPaymentIntent` for a hosted card payment |
| Freezing, unfreezing or closing an account | Not available | Contact support |

## Troubleshooting

| What you see | Why | Fix |
| :-- | :-- | :-- |
| Only two tools load | `GRAVV_API_KEY` is not set, so the server is in docs-only mode | Set the key and restart |
| `401` on every call | The API refused the key | Copy the whole current key |
| `404` for an id you know | The id is from the other environment | Check `environment` in the result |
| "exists but its toolset is not loaded" | The tool's toolset is off | Restart with `--toolsets=all`, or name the toolset |
| `confirmation_required` instead of a result | The tool moves money or cannot be undone | Review the preview, then call again with `confirm: true` |
| "moves money and this is a LIVE key" | `GRAVV_ALLOW_LIVE_WRITES` is not set | Set it only if you mean live payments |
| "is a sandbox-only test tool" | You called `simulateDeposit` with a key that is not a sandbox key | Use a `grvSec_sandbox_` key |
| A warning about a `grvPub_` key | Publishable keys cannot call the API | Use a secret key |
| `422` | You reused an `idempotency_key` with other arguments, or the API refused a transfer for a limit, a block or missing details | Read `code` and the hint |
| Repeated `429` | Another process uses the same Gravv account | Wait, then try again |
| `413` on an upload | The file is over the tool's limit, or over 500 KB in sandbox | Send a smaller file |
