Skip to main content

deribit_mcp/
lib.rs

1//! # `deribit-mcp`
2//!
3//! Model Context Protocol (MCP) server for the Deribit cryptocurrency
4//! derivatives platform. Single binary crate that adapts the
5//! `deribit-base`, `deribit-http`, `deribit-websocket`, and
6//! (optional) `deribit-fix` stack onto MCP's tool / resource /
7//! prompt surface.
8//!
9//! This is a **thin adapter** — every MCP tool is a translation
10//! step over an upstream call. Auth, rate limiting, reconnect, and
11//! wire codecs live in the sibling crates.
12//!
13//! ## Quick start
14//!
15//! The fastest path is Docker — clone the repo, drop credentials
16//! in `.env`, run compose. The binary also installs with
17//! `cargo install`.
18//!
19//! ### 1. Get credentials
20//!
21//! Create a Deribit API key at <https://test.deribit.com>
22//! (testnet) or <https://www.deribit.com> (mainnet). Account →
23//! API → Add new key. You only need `client_id` and
24//! `client_secret`. Public `Read` tools work without credentials;
25//! `Account` / `Trading` tools require them.
26//!
27//! ### 2. Configure
28//!
29//! Copy `.env.example` to `.env` at the repository root and fill
30//! in:
31//!
32//! ```bash
33//! DERIBIT_NETWORK=testnet                # or `mainnet`
34//! DERIBIT_CLIENT_ID=your_client_id
35//! DERIBIT_CLIENT_SECRET=your_client_secret
36//! # Optional — gates the HTTP endpoint with a static bearer:
37//! DERIBIT_HTTP_BEARER_TOKEN=
38//! RUST_LOG=info,deribit_mcp=debug
39//! ```
40//!
41//! `.env` is git-ignored (mode `600` recommended). Credentials
42//! never appear on the command line — they live in the
43//! environment only.
44//!
45//! ### 3. Deploy
46//!
47//! **Docker compose** (recommended):
48//!
49//! ```bash
50//! docker compose -f Docker/docker-compose.yml up -d
51//! curl -sf http://127.0.0.1:8723/healthz && echo "ok"
52//! ```
53//!
54//! **`docker run`**:
55//!
56//! ```bash
57//! docker build -f Docker/Dockerfile -t deribit-mcp:local .
58//! docker run -d --name deribit-mcp \
59//!   --restart unless-stopped \
60//!   --env-file .env \
61//!   -p 127.0.0.1:8723:8723 \
62//!   deribit-mcp:local \
63//!   --transport=http --listen=0.0.0.0:8723
64//! ```
65//!
66//! **`cargo install`**:
67//!
68//! ```bash
69//! cargo install --path . --locked
70//! deribit-mcp --transport=stdio
71//! # or
72//! deribit-mcp --transport=http --listen=0.0.0.0:8723
73//! ```
74//!
75//! The HTTP runtime image is distroless — no shell, no package
76//! manager, runs as `nonroot`. Health probe is `GET /healthz`
77//! (unauthenticated, returns `200 OK` while the server accepts
78//! requests).
79//!
80//! ### 4. Enable trading (opt-in)
81//!
82//! Trading tools (`place_order`, `edit_order`, `cancel_order`,
83//! `cancel_all_by_*`) are absent from `tools/list` unless you
84//! launch with `--allow-trading` AND credentials are configured.
85//! Add a notional cap to keep blast-radius bounded:
86//!
87//! ```bash
88//! deribit-mcp --transport=http --listen=0.0.0.0:8723 \
89//!   --allow-trading --max-order-usd=100
90//! ```
91//!
92//! The cap classifies inverse vs linear instruments and falls
93//! back to the upstream mark price for market orders that don't
94//! carry a price.
95//!
96//! ## Use from Claude Desktop
97//!
98//! Edit `~/Library/Application Support/Claude/claude_desktop_config.json`
99//! (macOS) or `%APPDATA%\Claude\claude_desktop_config.json`
100//! (Windows).
101//!
102//! ### Option A — HTTP (uses the running container)
103//!
104//! ```json
105//! {
106//!   "mcpServers": {
107//!     "deribit": {
108//!       "command": "npx",
109//!       "args": [
110//!         "-y",
111//!         "mcp-remote",
112//!         "http://127.0.0.1:8723/mcp"
113//!       ]
114//!     }
115//!   }
116//! }
117//! ```
118//!
119//! If you set `DERIBIT_HTTP_BEARER_TOKEN`, append
120//! `"--header", "Authorization: Bearer YOUR_TOKEN"` to `args`.
121//!
122//! ### Option B — stdio (no Docker)
123//!
124//! ```json
125//! {
126//!   "mcpServers": {
127//!     "deribit": {
128//!       "command": "/Users/you/.cargo/bin/deribit-mcp",
129//!       "args": ["--transport=stdio"],
130//!       "env": {
131//!         "DERIBIT_NETWORK": "testnet",
132//!         "DERIBIT_CLIENT_ID": "...",
133//!         "DERIBIT_CLIENT_SECRET": "..."
134//!       }
135//!     }
136//!   }
137//! }
138//! ```
139//!
140//! Quit Claude Desktop completely (⌘Q on macOS — including the
141//! menubar icon) and relaunch. The slash menu should now show
142//! `deribit:get_ticker`, `deribit:get_currencies`, etc.
143//!
144//! ## Sample prompts
145//!
146//! Paste any of these into Claude Desktop after the `deribit`
147//! connector is active. Each one exercises a specific tool,
148//! resource, or curated prompt; together they cover the full
149//! MCP surface.
150//!
151//! ### Public market data — `Read` tools (no credentials)
152//!
153//! - `get_server_time` — "Using the deribit MCP server, give me
154//!   the current Deribit server time as a Unix-ms timestamp."
155//! - `get_status` — "Using deribit, call `get_status` and tell me
156//!   if any currency is locked."
157//! - `get_currencies` — "Using deribit, list all currencies with
158//!   their long names, fee precision, and minimum withdrawal
159//!   fee."
160//! - `get_index_price` — "Using deribit, fetch the BTC USD index
161//!   price (`get_index_price`, `index_name=btc_usd`)."
162//! - `get_ticker` — "Using deribit, give me the BTC-PERPETUAL
163//!   ticker. Summarize in one line: mark, last, best bid, best
164//!   ask, 24h volume."
165//! - `get_instrument` — "Using deribit, call `get_instrument` for
166//!   `BTC-PERPETUAL` and tell me the contract size, tick size,
167//!   and minimum trade amount."
168//! - `list_instruments` — "Using deribit, list every active
169//!   `option` for `ETH` (`list_instruments`,
170//!   `currency=ETH, kind=option, expired=false`). Group by
171//!   expiration date."
172//! - `get_order_book` — "Using deribit, fetch the BTC-PERPETUAL
173//!   order book at depth 10 and show me the best 5 levels each
174//!   side."
175//! - `get_book_summary_by_currency` — "Using deribit, get a book
176//!   summary for every BTC future
177//!   (`get_book_summary_by_currency`, `currency=BTC,
178//!   kind=future`). Sort by 24h volume descending."
179//! - `get_book_summary_by_instrument` — "Using deribit, give me
180//!   the book summary for `BTC-PERPETUAL` only."
181//! - `get_last_trades` — "Using deribit, fetch the last 50 trades
182//!   on `BTC-PERPETUAL` (`get_last_trades`, `count=50`). Tell me
183//!   the buy/sell ratio."
184//! - `get_tradingview_chart_data` — "Using deribit, pull 1-hour
185//!   candles for BTC-PERPETUAL covering the last 24 hours via
186//!   `get_tradingview_chart_data`. Identify the hour with the
187//!   widest range."
188//! - `get_funding_rate_history` — "Using deribit, fetch
189//!   `get_funding_rate_history` for BTC-PERPETUAL covering the
190//!   last 24h. Compute the mean."
191//! - `get_historical_volatility` — "Using deribit, fetch
192//!   `get_historical_volatility` for BTC and ETH. Compare the
193//!   most recent values."
194//!
195//! ### Authenticated reads — `Account` tools (credentials
196//! required)
197//!
198//! - `get_account_summary` — "Using deribit, give me
199//!   `get_account_summary` for BTC with `extended=true`. Show
200//!   equity, initial margin, maintenance margin, P&L, available
201//!   balance."
202//! - `get_positions` — "Using deribit, list my open BTC
203//!   positions. Flag any with margin utilization above 70%."
204//! - `get_subaccounts` — "Using deribit, list my subaccounts
205//!   (`get_subaccounts`, `with_portfolio=true`)."
206//! - `get_transaction_log` — "Using deribit,
207//!   `get_transaction_log` for BTC over the last 24 hours. Group
208//!   by `type`."
209//! - `get_deposits` — "Using deribit, show my last 10 BTC
210//!   deposits (`get_deposits`, `currency=BTC, count=10`)."
211//! - `get_withdrawals` — "Using deribit, show my last 10 BTC
212//!   withdrawals."
213//! - `get_open_orders_by_currency` — "Using deribit, list every
214//!   open BTC order. Mark anything older than 24 hours."
215//! - `get_open_orders_by_instrument` — "Using deribit, list open
216//!   orders on `BTC-PERPETUAL` only."
217//! - `get_user_trades_by_currency` — "Using deribit, fetch the
218//!   last 50 BTC user trades (`get_user_trades_by_currency`,
219//!   `currency=BTC, count=50`). Compute net realized P&L."
220//! - `get_user_trades_by_instrument` — "Using deribit, fetch user
221//!   trades for `BTC-PERPETUAL` over the last 24 hours."
222//!
223//! ### Order entry — `Trading` tools (`--allow-trading`)
224//!
225//! Always test on testnet first. Use a small `--max-order-usd`
226//! cap.
227//!
228//! - `place_order` (limit buy) — "Using deribit, place a buy
229//!   limit order for 10 USD of `BTC-PERPETUAL` at half the
230//!   current mark price, post-only, label
231//!   `prompt-test`."
232//! - `place_order` (stop-limit) — "Using deribit, place a
233//!   stop-limit sell on `BTC-PERPETUAL`: amount 10, price 90000,
234//!   trigger_price 91000, trigger=`mark_price`."
235//! - `edit_order` — "Using deribit, edit order
236//!   `<order_id>` to amount 20 and price 51000."
237//! - `cancel_order` — "Using deribit, cancel order
238//!   `<order_id>`."
239//! - `cancel_all_by_currency` — "Using deribit, cancel every
240//!   open BTC order (`cancel_all_by_currency`, `currency=BTC`).
241//!   Tell me how many were cancelled."
242//! - `cancel_all_by_instrument` — "Using deribit, cancel every
243//!   open `BTC-PERPETUAL` order."
244//!
245//! ### Resources
246//!
247//! - `deribit://currencies` (static) — "Using deribit, read the
248//!   resource `deribit://currencies` and list each `currency`
249//!   with its `withdrawal_fee`."
250//! - `deribit://instruments/{currency}` (template) — "Using
251//!   deribit, read `deribit://instruments/BTC` and tell me how
252//!   many futures are listed."
253//! - `deribit://book/{instrument}` (live) — "Using deribit, read
254//!   `deribit://book/BTC-PERPETUAL` and show the top 5 bids and
255//!   asks."
256//! - `deribit://ticker/{instrument}` (live) — "Using deribit,
257//!   read `deribit://ticker/BTC-PERPETUAL`."
258//! - `deribit://trades/{instrument}` (live) — "Using deribit,
259//!   read `deribit://trades/BTC-PERPETUAL` and tell me the
260//!   trade-size distribution."
261//!
262//! ### Curated prompts
263//!
264//! MCP prompts are exposed as slash-commands in Claude Desktop —
265//! the user (not Claude) types them. Once the slash-command is
266//! expanded the curated message pair lands in the conversation
267//! and Claude follows the instructions, calling the tools the
268//! prompt names. The naming convention is
269//! `/mcp__<server>__<prompt>` (double underscores).
270//!
271//! - `daily_options_summary` — type
272//!   `/mcp__deribit__daily_options_summary` and supply
273//!   `currency=BTC`, `horizon_days=7`. Drives Claude through
274//!   `list_instruments`, `get_book_summary_by_currency`, and
275//!   `get_historical_volatility`, then produces a four-section
276//!   summary (ATM IV vs RV headline, top calls + puts by OI,
277//!   skew commentary, caveats).
278//! - `funding_snapshot` — type
279//!   `/mcp__deribit__funding_snapshot` and supply `currency=BTC`,
280//!   `lookback_hours=24`. Drives Claude through
281//!   `list_instruments` (perpetual filter) and
282//!   `get_funding_rate_history`, then produces a table (latest,
283//!   mean / median / p10 / p90, sign breakdown, outliers,
284//!   caveats).
285//! - `position_review` — type
286//!   `/mcp__deribit__position_review` and supply `currency=BTC`,
287//!   `include_history=true`. Drives Claude through
288//!   `get_account_summary`, `get_positions`,
289//!   `get_open_orders_by_currency`, and (when
290//!   `include_history=true`) `get_user_trades_by_currency`, then
291//!   produces a report (headline, positions, open orders, flags,
292//!   recent trades, caveats). When credentials are missing the
293//!   prompt returns a structured warning instead.
294//!
295//! If you want Claude to perform an equivalent workflow without
296//! invoking the slash-command, ask directly — for example: "Using
297//! deribit, summarize BTC options expiring in the next 7 days.
298//! Run `list_instruments` (kind=option), filter by
299//! `expiration_timestamp` within 7 days, then
300//! `get_book_summary_by_currency` and `get_historical_volatility`.
301//! Produce: ATM IV vs RV headline; top 3 calls and puts by open
302//! interest; skew commentary; caveats." That bypasses the
303//! curated prompt but reaches the same result.
304//!
305//! ### Negative paths (gating verification)
306//!
307//! - "Using deribit, try `place_order` with no credentials
308//!   configured. What does the registry say?" (Should be absent
309//!   from `tools/list`.)
310//! - "Using deribit, call `get_account_summary` for currency
311//!   `XYZ`." (Should return a structured `Validation` error.)
312//!
313//! ## Capability surface (v1.0.0)
314//!
315//! | Class | Count | Examples |
316//! |---|---:|---|
317//! | `Read` tools (no auth) | 14 | `get_ticker`, `get_currencies`, `get_order_book`, `get_book_summary_by_currency`, `get_funding_rate_history`, `get_historical_volatility`, `get_last_trades`, `get_tradingview_chart_data`, `get_index_price`, `get_server_time`, `get_status` |
318//! | `Account` tools (creds) | 10 | `get_account_summary`, `get_positions`, `get_subaccounts`, `get_open_orders_by_currency`, `get_user_trades_by_currency`, `get_transaction_log`, `get_deposits`, `get_withdrawals` |
319//! | `Trading` tools (`--allow-trading`) | 5 | `place_order`, `edit_order`, `cancel_order`, `cancel_all_by_currency`, `cancel_all_by_instrument` |
320//! | Resources | 1 + 4 templates | `deribit://currencies`, `deribit://instruments/{currency}`, `deribit://book/{instrument}`, `deribit://ticker/{instrument}`, `deribit://trades/{instrument}` |
321//! | Prompts | 3 | `daily_options_summary`, `funding_snapshot`, `position_review` |
322//!
323//! The MCP wire shape is locked under SemVer from 1.0.0 — adding
324//! / renaming / removing tool fields, resource URIs, error
325//! variants, or prompt arguments requires a 2.0 major bump (see
326//! `CHANGELOG.md`).
327//!
328//! ## Configuration reference
329//!
330//! | Setting | CLI | Env var | Default |
331//! |---|---|---|---|
332//! | Network | `--testnet` / `--mainnet` | `DERIBIT_NETWORK=testnet\|mainnet` or `DERIBIT_ENDPOINT=<url>` | testnet |
333//! | Credentials | — | `DERIBIT_CLIENT_ID` / `DERIBIT_CLIENT_SECRET` | none |
334//! | Trading opt-in | `--allow-trading` | `DERIBIT_ALLOW_TRADING=1` | off |
335//! | Notional cap | `--max-order-usd=N` | `DERIBIT_MAX_ORDER_USD=N` | unlimited |
336//! | MCP transport | `--transport=stdio\|http` | `DERIBIT_MCP_TRANSPORT` | stdio |
337//! | HTTP listen | `--listen=ADDR:PORT` | `DERIBIT_HTTP_LISTEN` | `127.0.0.1:8723` |
338//! | HTTP bearer | — | `DERIBIT_HTTP_BEARER_TOKEN` | none (anonymous) |
339//! | Order transport | `--order-transport=http\|fix` | `DERIBIT_ORDER_TRANSPORT` | http |
340//! | Log filter | — | `RUST_LOG` | `info` |
341//! | Log format | `--log-format=text\|json` | `DERIBIT_LOG_FORMAT` | text on stdio, json on http |
342//!
343//! CLI flags win over env vars; env vars win over `.env`; `.env`
344//! wins over built-in defaults.
345//!
346//! ## Observability
347//!
348//! Each MCP request emits two structured events on stderr (stdio)
349//! / stdout (http) — entry and outcome — with target
350//! `deribit_mcp::request` and `elapsed_ms`:
351//!
352//! ```bash
353//! docker logs -f deribit-mcp 2>&1 | grep deribit_mcp::request
354//! ```
355//!
356//! ```text
357//! INFO deribit_mcp::request tools/call tool="get_ticker"
358//! INFO deribit_mcp::request tools/call ok tool="get_ticker" elapsed_ms=75
359//! WARN deribit_mcp::request tools/call error tool="missing" elapsed_ms=0 error="..."
360//! ```
361//!
362//! Secrets (`client_secret`, `access_token`, `refresh_token`,
363//! `http_bearer_token`) are redacted before they reach any
364//! tracing sink.
365//!
366//! ## Troubleshooting
367//!
368//! | Symptom | Likely cause | Fix |
369//! |---|---|---|
370//! | `tools/call` returns `-32601 method not found` | Old image without the `ServerHandler::call_tool` override | Rebuild: `docker compose -f Docker/docker-compose.yml build --no-cache` |
371//! | Every `/mcp` request returns `401` | `DERIBIT_HTTP_BEARER_TOKEN` set in env (even empty `=` lines) | Remove the line from `.env` or pass `Authorization: Bearer ...` from the client |
372//! | `Upstream { error decoding response body }` | Custom endpoint URL missing `/api/v2` suffix | Use `DERIBIT_NETWORK=testnet\|mainnet` instead of `DERIBIT_ENDPOINT`; the canonical paths are auto-applied |
373//! | Account / Trading tools missing from `tools/list` | Credentials not loaded | Verify `docker inspect <id>` shows `DERIBIT_CLIENT_ID` and `DERIBIT_CLIENT_SECRET` |
374//! | Trading tools missing despite credentials | `--allow-trading` not set | Add it to the `command:` block in compose |
375//! | Claude Desktop says "connector not responding" | Cached old session in `mcp-remote` | Quit Claude Desktop with ⌘Q (also the menubar icon), relaunch |
376//! | `mcp-remote` rejects with "Invalid input in structuredContent" | Old server before scalar-wrap fix | Pull / rebuild — the adapter wraps non-object payloads in `{"value": ...}` since v1.0 |
377//!
378//! Build problems? `cargo install` requires `cmake`, `perl`,
379//! `pkg-config`, and `libssl-dev` (or `openssl-devel`) for the
380//! FIX dependency chain. Docker handles this automatically.
381//!
382//! ## Crate layout
383//!
384//! - [`config`] — CLI argument and `.env` resolution.
385//! - [`context`] — `AdapterContext` shared across handlers.
386//! - [`error`] — `AdapterError` and `From` impls for upstream errors.
387//! - [`server`] — `rmcp` Server scaffold.
388//! - [`observability`] — `tracing` setup and secret redaction.
389//! - [`tools`] — `Read` / `Account` / `Trading` tool families.
390//! - [`resources`] — static and live resource families.
391//! - [`prompts`] — curated MCP prompts (registry + handlers).
392//! - [`prelude`] — curated re-exports for downstream consumers.
393
394#![forbid(unsafe_code)]
395#![warn(missing_docs)]
396#![warn(rust_2018_idioms)]
397#![warn(clippy::all)]
398
399pub mod config;
400pub mod context;
401pub mod error;
402pub mod http_transport;
403pub mod observability;
404pub mod prelude;
405pub mod prompts;
406pub mod resources;
407pub mod server;
408pub mod tools;
409
410pub use crate::context::{AdapterContext, AuthState};
411pub use crate::error::{AdapterError, AuthFailureReason, UpstreamErrorKind};
412pub use crate::prompts::{PromptEntry, PromptHandlerFn, PromptRegistry};
413pub use crate::resources::{
414    ResourceContent, ResourceList, ResourceRegistry, ResourceUri, parse_resource_uri,
415};
416pub use crate::server::{DeribitMcpServer, MCP_PROTOCOL_VERSION};
417pub use crate::tools::{ToolClass, ToolEntry, ToolRegistry};