#!/usr/bin/env node
import dotenv from "dotenv";
import path from "path";
import { fileURLToPath } from "url";
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import axios from "axios";
import { Wallet } from "ethers";
// Load .env file from the same directory as this script
const __dirname = path.dirname(fileURLToPath(import.meta.url));
dotenv.config({ path: path.join(__dirname, ".env") });
// Environment configuration
const DERIVE_ENVIRONMENT = process.env.DERIVE_ENVIRONMENT || "mainnet";
const DERIVE_WALLET = process.env.DERIVE_WALLET || "";
const DERIVE_PRIVATE_KEY = process.env.DERIVE_PRIVATE_KEY || "";
// API endpoints
const API_BASES = {
mainnet: "https://api.lyra.finance",
testnet: "https://api-demo.lyra.finance",
};
const BASE_API_URL = API_BASES[DERIVE_ENVIRONMENT];
// Create wallet instance for signing
let wallet = null;
if (DERIVE_PRIVATE_KEY) {
try {
wallet = new Wallet(DERIVE_PRIVATE_KEY);
} catch (error) {
console.error("Failed to create wallet from private key:", error.message);
}
}
// Utility function to sign messages for wallet-based authentication (async)
async function signMessage(timestamp) {
try {
if (!wallet) return null;
// Use Ethereum personal_sign (EIP-191) - this is what Derive expects
return await wallet.signMessage(timestamp);
} catch (error) {
console.error("Signing error:", error.message);
return null;
}
}
// Utility function to make API requests
async function makeRequest(method, params = {}, requiresAuth = false) {
// Determine if this is a public or private endpoint
// Method format: "public/get_something" or "private/get_something"
const isPrivate = method.startsWith("private/");
const endpoint = isPrivate ? "/private/" : "/public/";
const methodName = method.replace(/^(public|private)\//, "");
const url = BASE_API_URL + endpoint + methodName;
const headers = {
"Content-Type": "application/json",
};
// Add wallet-based authentication if required
if (requiresAuth) {
if (!DERIVE_WALLET) {
throw new Error("DERIVE_WALLET environment variable is required for private endpoints");
}
const timestamp = Date.now().toString();
const signature = await signMessage(timestamp);
headers["X-LyraWallet"] = DERIVE_WALLET;
headers["X-LyraTimestamp"] = timestamp;
if (signature) {
headers["X-LyraSignature"] = signature;
}
}
try {
const response = await axios.post(url, params, { headers });
// Check for error in response
if (response.data.error) {
throw new Error(`API Error: ${JSON.stringify(response.data.error)}`);
}
// Derive API returns results directly in the response
return response.data.result || response.data;
} catch (error) {
if (error.response) {
throw new Error(`API request failed: ${error.response.status} - ${JSON.stringify(error.response.data)}`);
}
throw error;
}
}
// Create MCP server
const server = new Server(
{
name: "mcp-derive",
version: "1.0.0",
},
{
capabilities: {
tools: {},
},
}
);
// List available tools
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
// ========== PUBLIC MARKET DATA ENDPOINTS ==========
{
name: "get_currencies",
description: "Get all available currencies (underlying assets) on the platform.",
inputSchema: {
type: "object",
properties: {},
},
},
{
name: "get_instruments",
description: "Get all active instruments for a specified currency and type. Returns detailed information about tradeable assets including fees, constraints, and Greeks.",
inputSchema: {
type: "object",
properties: {
currency: {
type: "string",
description: "Underlying asset currency (e.g., ETH, BTC, SOL)",
},
expired: {
type: "boolean",
description: "Include expired assets (capped to 1 week past)",
default: false,
},
instrument_type: {
type: "string",
description: "Asset type",
enum: ["erc20", "option", "perp"],
},
},
required: ["currency", "instrument_type"],
},
},
{
name: "get_ticker",
description: "Get real-time ticker data for a single instrument (bid/ask, last price, 24h volume, Greeks for options).",
inputSchema: {
type: "object",
properties: {
instrument_name: {
type: "string",
description: "The instrument identifier (e.g., ETH-20250131-3000-C, BTC-PERP, USDC)",
},
},
required: ["instrument_name"],
},
},
{
name: "get_tickers",
description: "Get ticker information for multiple instruments at once. More efficient than get_ticker when querying many instruments.",
inputSchema: {
type: "object",
properties: {
currency: {
type: "string",
description: "Filter by currency (optional)",
},
instrument_type: {
type: "string",
description: "Filter by instrument type",
enum: ["erc20", "option", "perp"],
},
},
},
},
{
name: "get_orderbook",
description: "Get real-time orderbook data for a specific instrument with bid/ask levels, depth, and spread.",
inputSchema: {
type: "object",
properties: {
instrument_name: {
type: "string",
description: "The instrument identifier",
},
depth: {
type: "number",
description: "Number of price levels to return (default 10, max typically 100)",
default: 10,
},
},
required: ["instrument_name"],
},
},
// ========== HISTORICAL DATA ENDPOINTS ==========
{
name: "get_trade_history",
description: "Get tick-by-tick trade history with detailed trade information (price, size, buyer/seller, timestamp). Supports filtering and pagination.",
inputSchema: {
type: "object",
properties: {
instrument_name: {
type: "string",
description: "Filter by specific instrument name (optional)",
},
currency: {
type: "string",
description: "Filter by currency (optional)",
},
instrument_type: {
type: "string",
description: "Filter by instrument type",
enum: ["erc20", "option", "perp"],
},
from_timestamp: {
type: "number",
description: "Earliest timestamp in milliseconds since Unix epoch (default 0)",
},
to_timestamp: {
type: "number",
description: "Latest timestamp in milliseconds since Unix epoch (default now)",
},
page: {
type: "number",
description: "Page number (default 1)",
default: 1,
},
page_size: {
type: "number",
description: "Results per page (default 100, max 1000)",
default: 100,
},
trade_id: {
type: "string",
description: "Get specific trade by ID (overrides other filters)",
},
tx_hash: {
type: "string",
description: "Get trade by on-chain transaction hash",
},
tx_status: {
type: "string",
description: "Filter by transaction status",
enum: ["settled", "reverted", "timed_out"],
},
},
},
},
{
name: "get_funding_rate_history",
description: "Get historical funding rate data for perpetual contracts with timestamps and rates.",
inputSchema: {
type: "object",
properties: {
instrument_name: {
type: "string",
description: "Perpetual instrument (e.g., BTC-PERP, ETH-PERP) - optional to get all",
},
currency: {
type: "string",
description: "Filter by currency (optional)",
},
from_timestamp: {
type: "number",
description: "Earliest timestamp in milliseconds",
},
to_timestamp: {
type: "number",
description: "Latest timestamp in milliseconds",
},
page: {
type: "number",
description: "Page number (default 1)",
default: 1,
},
page_size: {
type: "number",
description: "Results per page (default 100, max 1000)",
default: 100,
},
},
},
},
{
name: "get_spot_feed_history",
description: "Get historical spot price feed data (index prices) with timestamps.",
inputSchema: {
type: "object",
properties: {
currency: {
type: "string",
description: "Currency to get spot feed for (e.g., BTC, ETH)",
},
from_timestamp: {
type: "number",
description: "Earliest timestamp in milliseconds",
},
to_timestamp: {
type: "number",
description: "Latest timestamp in milliseconds",
},
page: {
type: "number",
description: "Page number (default 1)",
default: 1,
},
page_size: {
type: "number",
description: "Results per page (default 100, max 1000)",
default: 100,
},
},
required: ["currency"],
},
},
{
name: "get_option_settlement_history",
description: "Get historical option settlement data (expiry, settlement prices, PnL).",
inputSchema: {
type: "object",
properties: {
currency: {
type: "string",
description: "Filter by currency (optional)",
},
from_timestamp: {
type: "number",
description: "Earliest timestamp in milliseconds",
},
to_timestamp: {
type: "number",
description: "Latest timestamp in milliseconds",
},
page: {
type: "number",
description: "Page number (default 1)",
default: 1,
},
page_size: {
type: "number",
description: "Results per page (default 100, max 1000)",
default: 100,
},
},
},
},
{
name: "get_liquidation_history",
description: "Get liquidation events history across the platform.",
inputSchema: {
type: "object",
properties: {
currency: {
type: "string",
description: "Filter by currency (optional)",
},
instrument_name: {
type: "string",
description: "Filter by instrument (optional)",
},
from_timestamp: {
type: "number",
description: "Earliest timestamp in milliseconds",
},
to_timestamp: {
type: "number",
description: "Latest timestamp in milliseconds",
},
page: {
type: "number",
description: "Page number (default 1)",
default: 1,
},
page_size: {
type: "number",
description: "Results per page (default 100, max 1000)",
default: 100,
},
},
},
},
// ========== PRIVATE ACCOUNT ENDPOINTS ==========
{
name: "get_account",
description: "Get account details including subaccounts, rate limits, fee structure, and account status. Requires authentication.",
inputSchema: {
type: "object",
properties: {
wallet: {
type: "string",
description: "Ethereum wallet address (defaults to DERIVE_WALLET env var)",
},
},
},
},
{
name: "get_subaccounts",
description: "Get all subaccounts for a wallet with labels and creation timestamps. Requires authentication.",
inputSchema: {
type: "object",
properties: {
wallet: {
type: "string",
description: "Ethereum wallet address (defaults to DERIVE_WALLET env var)",
},
},
},
},
{
name: "get_balance",
description: "Get account balance across all subaccounts including collaterals and open order margins. Requires authentication.",
inputSchema: {
type: "object",
properties: {
wallet: {
type: "string",
description: "Ethereum wallet address (defaults to DERIVE_WALLET env var)",
},
},
},
},
{
name: "get_positions",
description: "Get all open positions for a subaccount including unrealized PnL, mark price, leverage, and liquidation price. Requires authentication.",
inputSchema: {
type: "object",
properties: {
subaccount_id: {
type: "number",
description: "Subaccount ID to query positions for",
},
currency: {
type: "string",
description: "Filter by currency (optional)",
},
instrument_type: {
type: "string",
description: "Filter by instrument type",
enum: ["erc20", "option", "perp"],
},
},
required: ["subaccount_id"],
},
},
{
name: "get_collaterals",
description: "Get collateral information for a subaccount including amounts, prices, and margin contributions. Requires authentication.",
inputSchema: {
type: "object",
properties: {
subaccount_id: {
type: "number",
description: "Subaccount ID to query collaterals for",
},
},
required: ["subaccount_id"],
},
},
{
name: "get_margin",
description: "Get detailed margin information for a subaccount including maintenance margin, initial margin, and margin ratio. Requires authentication.",
inputSchema: {
type: "object",
properties: {
subaccount_id: {
type: "number",
description: "Subaccount ID to query margin for",
},
},
required: ["subaccount_id"],
},
},
// ========== TRADING ENDPOINTS ==========
{
name: "get_open_orders",
description: "Get all open orders for a subaccount with order status and details. Requires authentication.",
inputSchema: {
type: "object",
properties: {
subaccount_id: {
type: "number",
description: "Subaccount ID to query orders for",
},
currency: {
type: "string",
description: "Filter by currency (optional)",
},
instrument_name: {
type: "string",
description: "Filter by instrument name (optional)",
},
},
required: ["subaccount_id"],
},
},
{
name: "get_orders_history",
description: "Get order history for a subaccount with filled, cancelled, and rejected orders. Requires authentication.",
inputSchema: {
type: "object",
properties: {
subaccount_id: {
type: "number",
description: "Subaccount ID to query order history for",
},
currency: {
type: "string",
description: "Filter by currency (optional)",
},
instrument_name: {
type: "string",
description: "Filter by instrument name (optional)",
},
page: {
type: "number",
description: "Page number (default 1)",
default: 1,
},
page_size: {
type: "number",
description: "Results per page (default 100, max 1000)",
default: 100,
},
},
required: ["subaccount_id"],
},
},
{
name: "place_order",
description: "Place a new order (limit or market). Requires authentication and private key for signing.",
inputSchema: {
type: "object",
properties: {
subaccount_id: {
type: "number",
description: "Subaccount ID to place order on",
},
instrument_name: {
type: "string",
description: "Instrument to trade (e.g., BTC-PERP, ETH-20250131-3000-C)",
},
side: {
type: "string",
description: "Order side",
enum: ["buy", "sell"],
},
amount: {
type: "string",
description: "Order amount/quantity",
},
price: {
type: "string",
description: "Limit price (for limit orders)",
},
order_type: {
type: "string",
description: "Order type",
enum: ["limit", "market"],
default: "limit",
},
reduce_only: {
type: "boolean",
description: "Reduce only flag (default false)",
default: false,
},
post_only: {
type: "boolean",
description: "Post only flag - maker only (default false)",
default: false,
},
label: {
type: "string",
description: "Custom order label (optional)",
},
},
required: ["subaccount_id", "instrument_name", "side", "amount"],
},
},
{
name: "cancel_order",
description: "Cancel an open order by order ID. Requires authentication.",
inputSchema: {
type: "object",
properties: {
subaccount_id: {
type: "number",
description: "Subaccount ID that placed the order",
},
order_id: {
type: "string",
description: "Order ID to cancel",
},
},
required: ["subaccount_id", "order_id"],
},
},
{
name: "cancel_all_orders",
description: "Cancel all open orders for a subaccount. Optionally filter by currency or instrument. Requires authentication.",
inputSchema: {
type: "object",
properties: {
subaccount_id: {
type: "number",
description: "Subaccount ID",
},
currency: {
type: "string",
description: "Filter by currency (optional)",
},
instrument_name: {
type: "string",
description: "Filter by instrument (optional)",
},
},
required: ["subaccount_id"],
},
},
{
name: "replace_order",
description: "Replace an existing order (cancel old and place new) in one atomic operation. Requires authentication.",
inputSchema: {
type: "object",
properties: {
subaccount_id: {
type: "number",
description: "Subaccount ID",
},
order_id: {
type: "string",
description: "Order ID to replace",
},
amount: {
type: "string",
description: "New order amount",
},
price: {
type: "string",
description: "New order price",
},
},
required: ["subaccount_id", "order_id", "amount", "price"],
},
},
// ========== HISTORICAL ACCOUNT DATA ==========
{
name: "get_my_trades",
description: "Get personal trade history with detailed trade information (side, price, fees, PnL). Requires authentication.",
inputSchema: {
type: "object",
properties: {
subaccount_id: {
type: "number",
description: "Subaccount ID to get trades for",
},
instrument_name: {
type: "string",
description: "Filter by instrument (optional)",
},
currency: {
type: "string",
description: "Filter by currency (optional)",
},
from_timestamp: {
type: "number",
description: "Earliest timestamp in milliseconds",
},
to_timestamp: {
type: "number",
description: "Latest timestamp in milliseconds",
},
page: {
type: "number",
description: "Page number (default 1)",
default: 1,
},
page_size: {
type: "number",
description: "Results per page (default 100, max 1000)",
default: 100,
},
},
required: ["subaccount_id"],
},
},
{
name: "get_funding_history",
description: "Get funding payments history (paid and received). Requires authentication.",
inputSchema: {
type: "object",
properties: {
subaccount_id: {
type: "number",
description: "Subaccount ID",
},
instrument_name: {
type: "string",
description: "Filter by perpetual instrument (optional)",
},
currency: {
type: "string",
description: "Filter by currency (optional)",
},
from_timestamp: {
type: "number",
description: "Earliest timestamp in milliseconds",
},
to_timestamp: {
type: "number",
description: "Latest timestamp in milliseconds",
},
page: {
type: "number",
description: "Page number (default 1)",
default: 1,
},
page_size: {
type: "number",
description: "Results per page (default 100, max 1000)",
default: 100,
},
},
required: ["subaccount_id"],
},
},
{
name: "get_deposit_history",
description: "Get deposit history with transaction hashes and statuses. Requires authentication.",
inputSchema: {
type: "object",
properties: {
subaccount_id: {
type: "number",
description: "Subaccount ID",
},
from_timestamp: {
type: "number",
description: "Earliest timestamp in milliseconds",
},
to_timestamp: {
type: "number",
description: "Latest timestamp in milliseconds",
},
page: {
type: "number",
description: "Page number (default 1)",
default: 1,
},
page_size: {
type: "number",
description: "Results per page (default 100, max 1000)",
default: 100,
},
},
required: ["subaccount_id"],
},
},
{
name: "get_withdrawal_history",
description: "Get withdrawal history with transaction hashes and statuses. Requires authentication.",
inputSchema: {
type: "object",
properties: {
subaccount_id: {
type: "number",
description: "Subaccount ID",
},
from_timestamp: {
type: "number",
description: "Earliest timestamp in milliseconds",
},
to_timestamp: {
type: "number",
description: "Latest timestamp in milliseconds",
},
page: {
type: "number",
description: "Page number (default 1)",
default: 1,
},
page_size: {
type: "number",
description: "Results per page (default 100, max 1000)",
default: 100,
},
},
required: ["subaccount_id"],
},
},
// ========== RFQ (REQUEST FOR QUOTE) ENDPOINTS ==========
{
name: "send_rfq",
description: "Send a Request For Quote to get custom pricing from market makers. Requires authentication.",
inputSchema: {
type: "object",
properties: {
subaccount_id: {
type: "number",
description: "Subaccount ID",
},
instrument_name: {
type: "string",
description: "Instrument to request quote for",
},
side: {
type: "string",
description: "RFQ side",
enum: ["buy", "sell"],
},
amount: {
type: "string",
description: "Amount to request quote for",
},
},
required: ["subaccount_id", "instrument_name", "side", "amount"],
},
},
{
name: "get_rfqs",
description: "Get active RFQs for a subaccount with quotes received. Requires authentication.",
inputSchema: {
type: "object",
properties: {
subaccount_id: {
type: "number",
description: "Subaccount ID",
},
},
required: ["subaccount_id"],
},
},
{
name: "execute_quote",
description: "Execute a quoted trade at the quoted price. Requires authentication.",
inputSchema: {
type: "object",
properties: {
subaccount_id: {
type: "number",
description: "Subaccount ID",
},
quote_id: {
type: "string",
description: "Quote ID to execute",
},
},
required: ["subaccount_id", "quote_id"],
},
},
// ========== RISK MANAGEMENT ==========
{
name: "get_liquidation_price",
description: "Get current liquidation price for an open position. Requires authentication.",
inputSchema: {
type: "object",
properties: {
subaccount_id: {
type: "number",
description: "Subaccount ID",
},
instrument_name: {
type: "string",
description: "Instrument with open position",
},
},
required: ["subaccount_id", "instrument_name"],
},
},
{
name: "margin_watch",
description: "Get margin watch information showing accounts approaching liquidation. Requires authentication.",
inputSchema: {
type: "object",
properties: {
wallet: {
type: "string",
description: "Wallet address (defaults to DERIVE_WALLET env var)",
},
},
},
},
],
};
});
// Handle tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
let result;
let requiresAuth = false;
switch (name) {
// ========== PUBLIC ENDPOINTS ==========
case "get_currencies":
result = await makeRequest("public/get_all_currencies");
break;
case "get_instruments":
result = await makeRequest("public/get_instruments", {
currency: args.currency,
expired: args.expired ?? false,
instrument_type: args.instrument_type,
});
break;
case "get_ticker":
result = await makeRequest("public/get_ticker", {
instrument_name: args.instrument_name,
});
break;
case "get_tickers":
const tickersParams = {};
if (args.currency) tickersParams.currency = args.currency;
if (args.instrument_type) tickersParams.instrument_type = args.instrument_type;
result = await makeRequest("public/get_tickers", tickersParams);
break;
case "get_orderbook":
result = await makeRequest("public/get_orderbook", {
instrument_name: args.instrument_name,
depth: args.depth ?? 10,
});
break;
// ========== HISTORICAL DATA ==========
case "get_trade_history":
const tradeParams = {};
if (args.instrument_name) tradeParams.instrument_name = args.instrument_name;
if (args.currency) tradeParams.currency = args.currency;
if (args.instrument_type) tradeParams.instrument_type = args.instrument_type;
if (args.from_timestamp) tradeParams.from_timestamp = args.from_timestamp;
if (args.to_timestamp) tradeParams.to_timestamp = args.to_timestamp;
tradeParams.page = args.page ?? 1;
tradeParams.page_size = args.page_size ?? 100;
if (args.trade_id) tradeParams.trade_id = args.trade_id;
if (args.tx_hash) tradeParams.tx_hash = args.tx_hash;
if (args.tx_status) tradeParams.tx_status = args.tx_status;
result = await makeRequest("public/get_trade_history", tradeParams);
break;
case "get_funding_rate_history":
const fundingParams = {};
if (args.instrument_name) fundingParams.instrument_name = args.instrument_name;
if (args.currency) fundingParams.currency = args.currency;
if (args.from_timestamp) fundingParams.from_timestamp = args.from_timestamp;
if (args.to_timestamp) fundingParams.to_timestamp = args.to_timestamp;
fundingParams.page = args.page ?? 1;
fundingParams.page_size = args.page_size ?? 100;
result = await makeRequest("public/get_funding_rate_history", fundingParams);
break;
case "get_spot_feed_history":
result = await makeRequest("public/get_spot_feed_history", {
currency: args.currency,
from_timestamp: args.from_timestamp,
to_timestamp: args.to_timestamp,
page: args.page ?? 1,
page_size: args.page_size ?? 100,
});
break;
case "get_option_settlement_history":
const optionParams = {};
if (args.currency) optionParams.currency = args.currency;
if (args.from_timestamp) optionParams.from_timestamp = args.from_timestamp;
if (args.to_timestamp) optionParams.to_timestamp = args.to_timestamp;
optionParams.page = args.page ?? 1;
optionParams.page_size = args.page_size ?? 100;
result = await makeRequest("public/get_option_settlement_history", optionParams);
break;
case "get_liquidation_history":
const liquidationParams = {};
if (args.currency) liquidationParams.currency = args.currency;
if (args.instrument_name) liquidationParams.instrument_name = args.instrument_name;
if (args.from_timestamp) liquidationParams.from_timestamp = args.from_timestamp;
if (args.to_timestamp) liquidationParams.to_timestamp = args.to_timestamp;
liquidationParams.page = args.page ?? 1;
liquidationParams.page_size = args.page_size ?? 100;
result = await makeRequest("public/get_liquidation_history", liquidationParams);
break;
// ========== PRIVATE ACCOUNT ENDPOINTS ==========
case "get_account":
requiresAuth = true;
result = await makeRequest("private/get_account", {
wallet: args.wallet || DERIVE_WALLET,
}, requiresAuth);
break;
case "get_subaccounts":
requiresAuth = true;
result = await makeRequest("private/get_subaccounts", {
wallet: args.wallet || DERIVE_WALLET,
}, requiresAuth);
break;
case "get_balance":
requiresAuth = true;
result = await makeRequest("private/get_all_portfolios", {
wallet: args.wallet || DERIVE_WALLET,
}, requiresAuth);
break;
case "get_positions":
requiresAuth = true;
const positionsParams = {
subaccount_id: args.subaccount_id,
};
if (args.currency) positionsParams.currency = args.currency;
if (args.instrument_type) positionsParams.instrument_type = args.instrument_type;
result = await makeRequest("private/get_positions", positionsParams, requiresAuth);
break;
case "get_collaterals":
requiresAuth = true;
result = await makeRequest("private/get_collaterals", {
subaccount_id: args.subaccount_id,
}, requiresAuth);
break;
case "get_margin":
requiresAuth = true;
result = await makeRequest("private/get_margin", {
subaccount_id: args.subaccount_id,
}, requiresAuth);
break;
// ========== TRADING ENDPOINTS ==========
case "get_open_orders":
requiresAuth = true;
const openOrdersParams = {
subaccount_id: args.subaccount_id,
};
if (args.currency) openOrdersParams.currency = args.currency;
if (args.instrument_name) openOrdersParams.instrument_name = args.instrument_name;
result = await makeRequest("private/get_open_orders", openOrdersParams, requiresAuth);
break;
case "get_orders_history":
requiresAuth = true;
const ordersHistoryParams = {
subaccount_id: args.subaccount_id,
page: args.page ?? 1,
page_size: args.page_size ?? 100,
};
if (args.currency) ordersHistoryParams.currency = args.currency;
if (args.instrument_name) ordersHistoryParams.instrument_name = args.instrument_name;
result = await makeRequest("private/get_order_history", ordersHistoryParams, requiresAuth);
break;
case "place_order":
requiresAuth = true;
const orderParams = {
subaccount_id: args.subaccount_id,
instrument_name: args.instrument_name,
side: args.side,
amount: args.amount,
order_type: args.order_type ?? "limit",
reduce_only: args.reduce_only ?? false,
post_only: args.post_only ?? false,
};
if (args.price) orderParams.price = args.price;
if (args.label) orderParams.label = args.label;
result = await makeRequest("private/order", orderParams, requiresAuth);
break;
case "cancel_order":
requiresAuth = true;
result = await makeRequest("private/cancel", {
subaccount_id: args.subaccount_id,
order_id: args.order_id,
}, requiresAuth);
break;
case "cancel_all_orders":
requiresAuth = true;
const cancelAllParams = {
subaccount_id: args.subaccount_id,
};
if (args.currency) cancelAllParams.currency = args.currency;
if (args.instrument_name) cancelAllParams.instrument_name = args.instrument_name;
result = await makeRequest("private/cancel_all", cancelAllParams, requiresAuth);
break;
case "replace_order":
requiresAuth = true;
result = await makeRequest("private/replace", {
subaccount_id: args.subaccount_id,
order_id: args.order_id,
amount: args.amount,
price: args.price,
}, requiresAuth);
break;
// ========== HISTORICAL ACCOUNT DATA ==========
case "get_my_trades":
requiresAuth = true;
const myTradesParams = {
subaccount_id: args.subaccount_id,
page: args.page ?? 1,
page_size: args.page_size ?? 100,
};
if (args.instrument_name) myTradesParams.instrument_name = args.instrument_name;
if (args.currency) myTradesParams.currency = args.currency;
if (args.from_timestamp) myTradesParams.from_timestamp = args.from_timestamp;
if (args.to_timestamp) myTradesParams.to_timestamp = args.to_timestamp;
result = await makeRequest("private/get_trade_history", myTradesParams, requiresAuth);
break;
case "get_funding_history":
requiresAuth = true;
const fundingHistoryParams = {
subaccount_id: args.subaccount_id,
page: args.page ?? 1,
page_size: args.page_size ?? 100,
};
if (args.instrument_name) fundingHistoryParams.instrument_name = args.instrument_name;
if (args.currency) fundingHistoryParams.currency = args.currency;
if (args.from_timestamp) fundingHistoryParams.from_timestamp = args.from_timestamp;
if (args.to_timestamp) fundingHistoryParams.to_timestamp = args.to_timestamp;
result = await makeRequest("private/get_funding_history", fundingHistoryParams, requiresAuth);
break;
case "get_deposit_history":
requiresAuth = true;
const depositParams = {
subaccount_id: args.subaccount_id,
page: args.page ?? 1,
page_size: args.page_size ?? 100,
};
if (args.from_timestamp) depositParams.from_timestamp = args.from_timestamp;
if (args.to_timestamp) depositParams.to_timestamp = args.to_timestamp;
result = await makeRequest("private/get_deposit_history", depositParams, requiresAuth);
break;
case "get_withdrawal_history":
requiresAuth = true;
const withdrawalParams = {
subaccount_id: args.subaccount_id,
page: args.page ?? 1,
page_size: args.page_size ?? 100,
};
if (args.from_timestamp) withdrawalParams.from_timestamp = args.from_timestamp;
if (args.to_timestamp) withdrawalParams.to_timestamp = args.to_timestamp;
result = await makeRequest("private/get_withdrawal_history", withdrawalParams, requiresAuth);
break;
// ========== RFQ ENDPOINTS ==========
case "send_rfq":
requiresAuth = true;
result = await makeRequest("private/send_rfq", {
subaccount_id: args.subaccount_id,
instrument_name: args.instrument_name,
side: args.side,
amount: args.amount,
}, requiresAuth);
break;
case "get_rfqs":
requiresAuth = true;
result = await makeRequest("private/get_rfqs", {
subaccount_id: args.subaccount_id,
}, requiresAuth);
break;
case "execute_quote":
requiresAuth = true;
result = await makeRequest("private/execute_quote", {
subaccount_id: args.subaccount_id,
quote_id: args.quote_id,
}, requiresAuth);
break;
// ========== RISK MANAGEMENT ==========
case "get_liquidation_price":
requiresAuth = true;
// This endpoint might not exist directly, using get_positions as alternative
const positions = await makeRequest("private/get_positions", {
subaccount_id: args.subaccount_id,
}, requiresAuth);
// Filter to the specific instrument
const positionsResult = positions?.positions || [];
const position = positionsResult.find(p => p.instrument_name === args.instrument_name);
if (position) {
result = {
instrument_name: args.instrument_name,
liquidation_price: position.liquidation_price,
mark_price: position.mark_price,
amount: position.amount,
};
} else {
throw new Error(`No position found for ${args.instrument_name}`);
}
break;
case "margin_watch":
requiresAuth = true;
result = await makeRequest("public/margin_watch", {
wallet: args.wallet || DERIVE_WALLET,
}, requiresAuth);
break;
default:
throw new Error(`Unknown tool: ${name}`);
}
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Error: ${error.message}`,
},
],
isError: true,
};
}
});
// Start server
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Derive MCP Server running on stdio");
console.error(`Environment: ${DERIVE_ENVIRONMENT}`);
console.error(`API URL: ${BASE_API_URL}`);
console.error(`Wallet: ${DERIVE_WALLET || "Not configured"}`);
}
main().catch((error) => {
console.error("Fatal error:", error);
process.exit(1);
});