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

# MCP Server

> Give your AI assistant read-only access to your own LootRush account

## What it is

The LootRush MCP server lets an AI assistant — Claude, Cursor, or any
[Model Context Protocol](https://modelcontextprotocol.io) client — read **your own**
LootRush data on your behalf: your account balance, cards and card transactions,
and your account history.

Think of it as handing your agent a read-only window into your account. It can
answer "how much USDC do I have on Base right now?" or "list my card
transactions at Amazon last month" without you ever leaving the chat.

<Info>
  Every tool is **read-only** and **scoped to you**. The server resolves your
  identity from your API key — there is no parameter that lets a caller ask for
  someone else's data.
</Info>

## At a glance

|                |                                                                       |
| -------------- | --------------------------------------------------------------------- |
| **Endpoint**   | `https://mcp.lootrush.com/mcp`                                        |
| **Transport**  | Streamable HTTP (JSON-RPC 2.0), stateless — one request, one response |
| **Method**     | `POST` only                                                           |
| **Auth**       | Your LootRush API key (see [Authentication](#authentication))         |
| **Access**     | Read-only, scoped to the key's user                                   |
| **Rate limit** | 60 requests per 10 seconds, per user                                  |

## Connecting

You need two things: the endpoint above and your **LootRush API key** — the same
per-user key that authenticates the Withdraw and History APIs. Pass it as a Bearer
credential on every request.

<Warning>
  Treat your API key like a password. Anyone holding it can read your account
  data. Never paste it into a shared config that gets committed to source
  control, and rotate it if it leaks.
</Warning>

### Claude Code

```bash theme={null}
claude mcp add --transport http lootrush https://mcp.lootrush.com/mcp \
  --header "Authorization: Bearer YOUR_API_KEY"
```

### Claude Desktop / Cursor / other JSON-configured clients

Add LootRush to your client's MCP server list:

```json theme={null}
{
  "mcpServers": {
    "lootrush": {
      "url": "https://mcp.lootrush.com/mcp",
      "headers": {
        "Authorization": "Bearer YOUR_API_KEY"
      }
    }
  }
}
```

<Note>
  Some MCP clients can't attach custom headers to a remote server. For those,
  the server also accepts the key as a query parameter:
  `https://mcp.lootrush.com/mcp?token=YOUR_API_KEY`. The `Authorization` header
  is preferred — a key in a URL is easier to leak through logs and browser
  history.
</Note>

## Authentication

Authentication uses your **LootRush API key** — the same per-user key the Withdraw
and History APIs accept. The server validates the key, resolves the account it
belongs to, then scopes every tool to that user. A key maps to exactly one user,
so tools can only ever reach that user's own data.

The server looks for your key in this order:

1. **`Authorization: Bearer <api-key>`** header — preferred.
2. **`?token=<api-key>`** query parameter — fallback for clients that can't set headers.

Cookies are deliberately **not** accepted. Because this endpoint is public and
cross-origin, an ambient cookie would be a drive-by (CSRF) risk, so callers must
present the key explicitly.

<Warning>
  A valid API key is necessary but not sufficient — your key must be **scoped**
  for the MCP. Every request needs the `mcp` scope; without it a good key still
  returns `403`. If your key has an **IP allowlist**, requests from other IPs
  return `403` too. Scopes and the allowlist are set when the key is created —
  see [Settings → API keys](https://www.lootrush.com/tokens/s/settings?tab=api-key).
</Warning>

<Info>
  Generate your account API key at
  [Settings → API Key](https://www.lootrush.com/tokens/s/settings?tab=api-key).
  The same key works across the Withdraw, History, and MCP APIs.
</Info>

## Available tools

All tool names are called exactly as written (camelCase). Every result is scoped
to the authenticated user.

<CardGroup cols={2}>
  <Card title="getAccountBalance" icon="chart-pie">
    Your total account value in USD — crypto holdings, staked balances, and
    tokenized assets, exactly like the portfolio Summary.
  </Card>

  <Card title="getUserCards" icon="credit-card">
    Your cards across all issuers, with aggregated balances. Optionally reveals a
    single card's number and CVV — end-to-end encrypted, decrypted only on your
    client.
  </Card>

  <Card title="getUserCardTransactions" icon="receipt">
    Card transactions across all cards, or a single card.
  </Card>

  <Card title="getAccountHistory" icon="table-list">
    Your account history — deposits, withdrawals, converts, sends, and card
    operations — by date window, or one transaction by hash/id.
  </Card>
</CardGroup>

### getAccountBalance

Returns the total USD value of your account — everything shown in your portfolio
Summary: crypto holdings, staked balances, and tokenized assets (stocks/ETFs)
plus their profit/loss — **and a per token/network breakdown** so the balance can
be visualized, not just totalled. Takes no parameters. All values are
human-readable strings.

If your balance snapshot is more than **5 minutes** old, the tool triggers a
synchronous on-chain refresh and re-reads before responding — so you get numbers
no more than \~5 minutes stale. (The refresh runs at most once per 5 minutes per
account; if it can't complete it falls back to the last snapshot.) `lastFetchedAt`
tells you when the returned data was captured.

| Field                     | Description                                                                                                                                                          |
| ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `totalUsdValue`           | Holdings + staked + tokenized assets                                                                                                                                 |
| `holdingUsdValue`         | Crypto holdings (excludes operational stablecoins)                                                                                                                   |
| `stakedUsdValue`          | Staked balances                                                                                                                                                      |
| `tokenizedAssetsUsdValue` | Tokenized assets (stocks/ETFs)                                                                                                                                       |
| `tokenizedAssetsPnl`      | Combined realized + unrealized P/L (may be negative; `null` if unavailable). `tokenizedAssetsPnlPartial: true` is added when only one of the two P/L reads succeeded |
| `byToken`                 | Array of per-holding rows, biggest first: `{ token, network, isStake, amount, balanceUsd }` — `amount` is the human token amount, `balanceUsd` its USD value         |
| `tokenizedAssetsError`    | Present only when the tokenized-asset value read failed — the total then **excludes** it rather than silently counting it as `$0`                                    |
| `lastFetchedAt`           | When the snapshot was taken (ISO 8601, or `null`)                                                                                                                    |
| `isStale`                 | `true` when the snapshot is missing or older than 24h                                                                                                                |

### getUserCards

Returns your cards across all issuers plus aggregated balances. Each card carries
its `cardId` and `cardIssuerUserId` — pass those to `getUserCardTransactions` to
scope to a single card, or back into this tool (with `includeCardSecrets`) to
reveal that card's number and CVV.

| Parameter            | Type    | Required | Description                                                                                                              |
| -------------------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------------------ |
| `cardId`             | string  | No       | Filter to one card (from a prior `getUserCards`)                                                                         |
| `cardIssuerUserId`   | string  | No       | Required alongside `cardId` to target a specific card                                                                    |
| `includeCardSecrets` | boolean | No       | Reveal the card number (PAN) and CVV — **encrypted** (see below). Requires `cardId`, `cardIssuerUserId`, and `sessionId` |
| `sessionId`          | string  | No       | Your RSA-wrapped AES key. Required when `includeCardSecrets` is `true`                                                   |

#### Revealing card number & CVV

<Warning>
  The server **never** decrypts your card secrets. It returns only the issuer's
  ciphertext, so the number and CVV never pass through the model, the transport,
  or our logs in plaintext. Decryption happens **only on your client**, with a key
  that never leaves it.
</Warning>

Because of that, this is an **advanced** flow — your MCP client must run a small
encryption handshake. A plain chat client can't do it; wrap the tool in your own
client (Node/Python/Bash) that implements the two steps below.

Revealing secrets requires more than the base `mcp` scope: your API key must
also carry the **`mcp_card_reveal`** scope (a plain `mcp` key can list cards but
never reveal secrets), you need **read** permission on the `card-details` feature
(stricter than the plain card list), and it is scoped to a card you own.

**The handshake**

1. Fetch the reveal public key with **`getCardRevealPublicKey`**. Generate a
   random **AES-128** key locally and RSA-OAEP–wrap it with that key (SHA-1
   hash). The base64 of the wrapped key is your `sessionId`.
2. Call `getUserCards` with `includeCardSecrets: true`, the target `cardId` /
   `cardIssuerUserId`, and your `sessionId`. The response carries
   `cardSecrets.encryptedPan` and `cardSecrets.encryptedCvc`, each
   `{ base64Secret, base64Iv }` — AES-GCM ciphertext.
3. Decrypt each locally with the AES key you kept in step 1.

<Tip>
  **You can do step 1 once and reuse it.** The AES key doesn't need to be
  ephemeral — generate one key, derive the `sessionId` a single time, and store
  both. Every later reveal just passes the stored `sessionId` and decrypts with
  the stored key. It stays secure because each response ships a fresh
  `base64Iv`, so reusing the AES key never reuses a nonce. Keep the key secret
  and on your side only — treat it like the card data it unlocks.
</Tip>

Reveals share one **per-account** budget (10 per minute across all your cards,
not per card); beyond that the tool returns a "too many card reveals" error —
back off and retry.

<Warning>
  **Your API key alone can reveal card details.** Unlike the LootRush web app,
  which requires an interactive step-up (re-auth) before showing a card number,
  the MCP reveal is authorized by your API key plus the per-account access gate —
  there is no additional step-up. Treat any key that can reach this endpoint as
  equivalent to the card data it unlocks: keep it only in a trusted MCP client,
  never commit or sync it, and rotate it immediately if it may have leaked. Every
  reveal is recorded in a server-side audit log (card id and outcome only — never
  the number, CVV, or your session key).
</Warning>

<Note>
  You never hardcode the key: `getCardRevealPublicKey` serves the current reveal
  public key, so LootRush can rotate it without any change on your side. It also
  returns the wrapping algorithm (`RSA-OAEP`, SHA-1) so you don't have to guess.
</Note>

#### getCardRevealPublicKey (reveal helper)

The helper tool behind step 1. Takes no parameters; fetch it once and cache the
result — it changes only when LootRush rotates the key.

| Field       | Description                                             |
| ----------- | ------------------------------------------------------- |
| `publicKey` | RSA public key (PEM) to RSA-OAEP–wrap your AES key with |
| `algorithm` | Wrapping algorithm — `RSA-OAEP`                         |
| `hash`      | OAEP hash — `SHA-1`                                     |
| `usage`     | Short reminder of the wrap-and-decrypt handshake        |

**Node example**

```js theme={null}
import crypto from "node:crypto";

// Fetch once from the getCardRevealPublicKey tool (result.publicKey), then cache.
const ISSUER_PUBLIC_KEY = await fetchRevealPublicKey(); // PEM

// Step 1 — build the sessionId (keep secretKeyHex; you need it to decrypt).
const secretKeyHex = crypto.randomBytes(16).toString("hex"); // AES-128 key
const wrappedKey = crypto.publicEncrypt(
  {
    key: ISSUER_PUBLIC_KEY,
    padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
    oaepHash: "sha1",
  },
  // The issuer expects the base64 of the raw key bytes, RSA-wrapped.
  Buffer.from(Buffer.from(secretKeyHex, "hex").toString("base64"), "utf8")
);
const sessionId = wrappedKey.toString("base64");

// Step 2 — call getUserCards with includeCardSecrets + sessionId (omitted here).
// Step 3 — decrypt the returned ciphertext locally.
function decryptSecret({ base64Secret, base64Iv }, secretKeyHex) {
  const key = Buffer.from(secretKeyHex, "hex");
  const iv = Buffer.from(base64Iv, "base64");
  const blob = Buffer.from(base64Secret, "base64");
  // WebCrypto AES-GCM appends the 16-byte auth tag to the ciphertext.
  const tag = blob.subarray(blob.length - 16);
  const data = blob.subarray(0, blob.length - 16);
  const decipher = crypto.createDecipheriv("aes-128-gcm", key, iv);
  decipher.setAuthTag(tag);
  return Buffer.concat([decipher.update(data), decipher.final()]).toString("utf8");
}

// const pan = decryptSecret(cardSecrets.encryptedPan, secretKeyHex);
// const cvv = decryptSecret(cardSecrets.encryptedCvc, secretKeyHex);
```

### getUserCardTransactions

Fetches your card transactions across all issuers, or a single card's when
`cardId` is given.

| Parameter          | Type   | Required | Description                                           |
| ------------------ | ------ | -------- | ----------------------------------------------------- |
| `cardId`           | string | No       | Scope to one card (from `getUserCards`)               |
| `cardIssuerUserId` | string | No       | Required alongside `cardId` to target a specific card |
| `page`             | number | No       | 1-indexed page number (default `1`)                   |
| `pageSize`         | number | No       | Items per page, 1–100                                 |
| `startDate`        | string | No       | ISO 8601 lower bound                                  |
| `endDate`          | string | No       | ISO 8601 upper bound                                  |
| `status`           | string | No       | One of `on_hold`, `reversed`, `declined`, `settled`   |
| `polarity`         | string | No       | One of `debit`, `credit`                              |

### getAccountHistory

Your account history as report transactions — deposits, withdrawals, converts,
sends, and card operations, joined with their source records (amounts
human-readable). Returns the newest transactions first. Pass `filterByHash` or
`filterByOrderId` to fetch a single transaction (this replaces the date window);
otherwise it returns the window bounded by `startDate`/`endDate`, capped by
`limit`.

| Parameter         | Type    | Required | Description                                                                                                |
| ----------------- | ------- | -------- | ---------------------------------------------------------------------------------------------------------- |
| `filterByHash`    | string  | No       | Fetch only the transaction(s) with this on-chain transaction hash                                          |
| `filterByOrderId` | string  | No       | Fetch only the transaction(s) with this order id (the source record id)                                    |
| `startDate`       | string  | No       | ISO 8601 lower bound (ignored when a filter is set)                                                        |
| `endDate`         | string  | No       | ISO 8601 upper bound (ignored when a filter is set)                                                        |
| `showFee`         | boolean | No       | Include fee lines (default `true`)                                                                         |
| `limit`           | number  | No       | Max transactions to return (default `100`, max `500`). Response sets `truncated: true` when the cap is hit |

## Access & scoping

The MCP server is built so a tool can only ever reach the caller's own data:

* **No identity selector.** Your account is resolved from the API key at
  connection time and captured by every tool. There is no `userId` argument to
  spoof.
* **Business roles are honored.** If you're a member of a business, a tool that
  reads a resource runs only when your role grants **read** permission for that
  feature. Individual accounts and business owners are unrestricted over their
  own data.
* **Read-only.** No tool mutates anything. The server moves no funds and changes
  no settings.

## Rate limits

Each user may make up to **60 requests every 10 seconds**. Beyond that, the
server returns a `429` with JSON-RPC error code `-32005`:

```json theme={null}
{
  "jsonrpc": "2.0",
  "error": { "code": -32005, "message": "Rate limit exceeded" },
  "id": null
}
```

If you hit the limit, back off and retry with exponential delay.

## Errors

Errors come back as a standard JSON-RPC 2.0 error envelope. Stack traces are
never exposed to the client.

| HTTP  | JSON-RPC code | Meaning                                                                        |
| ----- | ------------- | ------------------------------------------------------------------------------ |
| `401` | `-32001`      | `Unauthorized` — missing, invalid, or expired API key                          |
| `403` | `-32003`      | Valid key, but it lacks the `mcp` scope (or `mcp_card_reveal` for a reveal)    |
| `403` | `-32004`      | Valid key, but this request's IP isn't in the key's IP allowlist               |
| `429` | `-32005`      | `Rate limit exceeded` — see [Rate limits](#rate-limits)                        |
| `405` | `-32000`      | Method not allowed — this is a stateless server; only `POST /mcp` is supported |

Request bodies are capped at 256 KB.

## Support

* Email: [support@lootrush.com](mailto:support@lootrush.com)
* Dashboard: [LootRush Dashboard](https://www.lootrush.com/tokens/s/dashboard)
