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

# MCP Server

> What the Joyride MCP tools expose, how to connect an AI client, the spend ceiling an operator must configure, and why the server cannot sign.

The Joyride MCP server lets an AI client read market and RFQ state and request
quotes on option packages. It ships inside the `joyride-cli` package and speaks
MCP over stdio.

It is a reading and requesting surface, not a full trading surface. Read
[The server holds no signing key](#the-server-holds-no-signing-key) before
planning anything around it.

## Connect a client

Install the CLI, sign in, then register the server with a client:

```bash theme={null}
npm install -g joyride-cli
joyride login
joyride mcp install --client claude
```

`--client` accepts `claude`, `codex`, or `cursor`. Pass `--force` to overwrite
an existing entry, and `joyride mcp uninstall --client <client>` to remove it.

To register it by hand, the server entry is the `joyride` binary with the
`mcp serve` arguments:

```json theme={null}
{
  "mcpServers": {
    "joyride": {
      "command": "joyride",
      "args": ["mcp", "serve"]
    }
  }
}
```

The server is a pure auth client: it reads the session token that
`joyride login` wrote and sends it as a bearer for every downstream call. It
never runs Sign-In with Solana itself and never opens the keystore. If the
session has expired, every tool reports that and points back at
`joyride login`. A wallet selected on the command line that does not match the
stored session is a fatal startup error, so one wallet's session can never be
used to act as another.

## What the RFQ tools expose

| Tool                    | What it does                                                                                                                               |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `request_rfq_quote`     | Request a firm quote on a package of 1 to 13 legs. Blocks for the venue's request window and returns the quote, or a plain unquoted result |
| `accept_rfq_quote`      | Accept one side of a live quote. Commits collateral. See the two sections below                                                            |
| `get_rfq`               | One RFQ: state, legs, the package quote, and the funding view                                                                              |
| `list_rfqs`             | This account's live RFQ requests                                                                                                           |
| `get_rfq_fills`         | Durable RFQ fill history, newest first. RFQ books purge at settlement, so this is the record that survives                                 |
| `get_rfq_account`       | The vault subaccount behind this account: whether it exists, and the free and locked balances                                              |
| `ensure_rfq_subaccount` | Ask Joyride to provision this wallet's vault subaccount. Safe to call when it already exists                                               |

Package legs are ordered and their quantities are signed decimal strings:
positive buys the leg, negative sells it. Submitted order is canonical and is
never sorted.

`request_rfq_quote` takes an optional `client_ref` idempotency key. When a
request's outcome comes back unknown, re-sending the same key retries that
request rather than opening a second one.

Market data and account reads are available through the server's other tools
and resources. Order-book trading tools also exist from the exchange's earlier
shape; the venue these pages document is RFQ, and nothing here teaches the
order book.

Three maker tools — `watch_rfq_requests`, `respond_rfq_quote`, and
`decline_rfq_request` — exist behind an operator switch and are absent from the
tool listing unless it is on. Quoting from MCP is limited by the same missing
key described below, so a desk should read [TypeScript Maker SDK](/agents/maker-sdk)
instead.

## The server holds no signing key

This is a design decision, not a gap to configure around.

The MCP server is a pure auth client. It resumes a session token; it never
opens the keystore and never produces an ed25519 signature. Accepting a quote
and responding to a request are both signing operations, so in production:

* `accept_rfq_quote` cannot sign an acceptance.
* `respond_rfq_quote` cannot sign a maker quote. Its refusal says so in those
  words and points at the CLI.

**No environment variable enables signing here.** There is no flag, no setting,
and no configuration file entry that turns it on. An agent that goes looking
for one is looking for something that does not exist.

Signing happens in the `joyride` CLI, which holds the keystore, or in the
Joyride app. A workable division of labour is: let the agent find the package
and request the quote through MCP, then accept from the CLI with
[`joyride rfq accept`](/agents/cli#consent-and-operator-ceilings).

A refused accept consumes nothing. No nonce is spent, no request state changes,
and the account's other live requests are unaffected.

## The spend ceiling

`accept_rfq_quote` is gated by two environment variables before it touches
anything, and the gate is a safety requirement rather than an optional tweak.
They are required together: with neither set, or with only one set, every
accept refuses. A half-configured operator would otherwise believe spend is
capped while a whole dimension stayed open.

`JOYRIDE_RFQ_MAX_PREMIUM_USDC` caps the total cash one accept may cost.&#x20;

`JOYRIDE_RFQ_MAX_LOCKED_USDC` caps the collateral one accept may lock.&#x20;

Set both in the `env` object of the server entry in your client's
configuration, or export them in the shell that launches the server. Values are
plain USDC decimals, for example `250.00`.

### Total cash, not premium alone

That first cap is a cost ceiling, not a premium ceiling. It bounds the
**premium plus the trading fee maximum the signature authorizes**, compared as
one number. The fee is real
money leaving the account, and it is charged and capped per leg rather than per
package, so it grows with the number of legs while the premium does not have
to. A package can therefore owe far more in fees than its premium suggests; the
worked example on [Fees and Limits](/market-makers/fees-and-limits) shows one.

**A credit package is not automatically under the ceiling.** A credit pays the
taker, so its premium contributes nothing to the cost — but the fee is still
owed, and it is the whole cost. Those are exactly the structures a
premium-only ceiling never bounded at all.

<Warning>
  This changed the meaning of a variable you may already have set. A ceiling
  pitched at exactly what a package's premium costs
  now refuses that package, because the fee pushes the total over it. If accepts
  start refusing with `max_premium` after an upgrade, that is the cause: raise
  the ceiling to cover the premium and the fee together.
</Warning>

The same two caps bound the maker tools when they are switched on, with the
worst-case package outflow standing in for the premium: a quote is refused when
that outflow plus the `max_maker_fee` it signs for is above the cost ceiling.
The refusal names the bound, the limit, the figure and the overshoot.

Three properties are deliberate:

* The ceilings are the operator's, and `accept_rfq_quote` exposes no arguments
  for them. A model cannot pass what it cannot name, so it cannot raise them.
* They are read at call time, not at startup, so there is no stale captured
  copy to disagree with the environment.
* They are handed to the SDK, which evaluates them against the same read the
  signature is built from. A pre-check would test one read and sign against
  another.

The tool stays listed when the ceiling is unset. An agent that can see it can
explain why it cannot act; a tool that vanishes looks like a bug, and a bug
invites a workaround.

Because the server holds no key, an accept refuses even with both ceilings set.
Configure them anyway: they are the guard that is already in place for any
future signing path.

## What an agent can and cannot do

| Can                                                                        | Cannot                                            |
| -------------------------------------------------------------------------- | ------------------------------------------------- |
| Read instruments, prices, balances, positions, and fills                   | Sign anything                                     |
| Read RFQ state: `get_rfq`, `list_rfqs`, `get_rfq_fills`, `get_rfq_account` | Accept a quote with `accept_rfq_quote`            |
| Open a request with `request_rfq_quote`                                    | Deliver a maker quote with `respond_rfq_quote`    |
| Provision the vault subaccount with `ensure_rfq_subaccount`                | Move money: deposits and withdrawals are app-only |

## There is no cancel and no replace

A quote delivered to the venue is firm until its `expires_at`. Nothing pulls it
back, amends it, or replaces it, and the quote's TTL is the only control over
how long that commitment stands.

For an agent on the taking side, the consequence is on the clock: a quote that
is read and reasoned about for too long expires, and the venue refuses an
accept that arrives with under two seconds of life left. Treat `expires_at` as
a deadline, not a hint, and re-request rather than accepting stale state.

## Support

[support@joyride.exchange](mailto:support@joyride.exchange).
