UNPKG

@dbotx/limit-order-mcp-server

Version:

Limit Order MCP Server - Supporting multi-chain limit orders, precise price triggers, and flexible order management. A professional cross-chain MCP trading service supporting DEX trading on multiple chains including Solana, Ethereum, BSC and more.

749 lines (744 loc) 35.1 kB
#!/usr/bin/env node "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js"); const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js"); const types_js_1 = require("@modelcontextprotocol/sdk/types.js"); const client_js_1 = require("./client.js"); const types_js_2 = require("./types.js"); class LimitOrderMcpServer { server; client; constructor() { this.server = new index_js_1.Server({ name: 'limit-order-mcp-server', version: '1.0.0', }, { capabilities: { tools: {}, }, }); this.client = new client_js_1.DbotLimitOrderClient(); this.setupToolHandlers(); this.setupErrorHandling(); } setupErrorHandling() { this.server.onerror = (error) => { console.error('[MCP Error]', error); }; process.on('SIGINT', async () => { await this.server.close(); process.exit(0); }); } setupToolHandlers() { this.server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => { return { tools: [ { name: 'create_limit_order', description: `Create multi-chain limit buy and limit sell orders. Supports creating multiple limit orders in one call. Example 1: Create a single buy order { "chain": "solana", "pair": "BAS6Cv5qG2FnEQ8W1wXKGfU2r6MFAiSdgGgkUM7NsStE", "settings": [{ "enabled": true, "tradeType": "buy", "triggerPriceUsd": "0.022", "triggerDirection": "down", "currencyAmountUI": 0.1 }] } Example 2: Create buy and sell orders simultaneously { "chain": "solana", "pair": "BAS6Cv5qG2FnEQ8W1wXKGfU2r6MFAiSdgGgkUM7NsStE", "settings": [ { "enabled": true, "tradeType": "buy", "triggerPriceUsd": "0.022", "triggerDirection": "down", "currencyAmountUI": 0.1 }, { "enabled": true, "tradeType": "sell", "triggerPriceUsd": "1.5", "triggerDirection": "up", "currencyAmountUI": 1 } ] }`, inputSchema: { type: 'object', properties: { chain: { type: 'string', enum: ['solana', 'ethereum', 'base', 'bsc', 'tron'], description: '(Optional) Blockchain name (solana/ethereum/base/bsc/tron), defaults to solana if not specified.' }, pair: { type: 'string', description: '(Required) Token address or trading pair address to buy/sell' }, walletId: { type: 'string', description: '(Optional) ID of the wallet to use' }, groupId: { type: 'string', description: '(Optional) Group ID for order management' }, settings: { type: 'array', description: 'List of limit order settings, can create multiple buy and sell orders simultaneously.', items: { type: 'object', properties: { enabled: { type: 'boolean', description: '(Optional) Order enabled status, defaults to true' }, tradeType: { type: 'string', enum: ['buy', 'sell'], description: 'Trade type: buy for buying, sell for selling' }, triggerPriceUsd: { type: 'string', description: 'Price in USD that triggers buy/sell' }, triggerDirection: { type: 'string', enum: ['up', 'down'], description: '"down" executes when price falls below trigger, "up" executes when price rises above trigger' }, currencyAmountUI: { type: ['string', 'number'], description: 'For buy orders: amount in ETH/SOL/BNB/TRX. For sell orders: proportion to sell (0.00-1.00)' }, customFeeAndTip: { type: 'boolean', description: 'Enable custom fees. True means both priority fee and tip are valid, false means only priority fee in high speed mode and only tip in anti-sandwich mode. Defaults to false' }, priorityFee: { type: 'string', description: 'Priority fee (SOL), valid for Solana, empty string means auto priority fee. Defaults to "0.0001"' }, gasFeeDelta: { type: 'number', description: 'Additional gas (Gwei), valid for EVM chains. Defaults to 5' }, maxFeePerGas: { type: 'number', description: 'Transaction will not execute if base gas exceeds this value (Gwei), valid for EVM chains. Defaults to 100' }, jitoEnabled: { type: 'boolean', description: 'Enable anti-sandwich mode (Solana & Ethereum & Bsc). Defaults to true' }, jitoTip: { type: 'number', description: 'Tip used in anti-sandwich mode (Solana). Defaults to 0.001' }, expireDelta: { type: 'number', description: 'Task validity duration, maximum 432000000 (milliseconds). Defaults to 360000000' }, expireExecute: { type: 'boolean', description: 'Execute on expiry. If true and task has not triggered by expiry, will buy/sell at current market price. Defaults to false' }, useMidPrice: { type: 'boolean', description: 'Enable anti-spike mode, uses 1-second median price as trigger price, helps avoid but cannot completely prevent price spikes. Defaults to false' }, maxSlippage: { type: 'number', description: 'Maximum slippage (0.00-1.00). For buys: difference between actual and expected price. For sells: difference between expected and actual price. Defaults to 0.1' }, concurrentNodes: { type: 'number', description: 'Number of concurrent nodes (1-3). Defaults to 2' }, retries: { type: 'number', description: 'Number of retries on failure (0-10). Defaults to 1' } }, required: ['tradeType', 'triggerPriceUsd', 'triggerDirection', 'currencyAmountUI'], }, }, }, required: ['pair', 'settings'], }, }, { name: 'edit_limit_order', description: `Edit a limit order, only generate parameters the user wants to modify along with required parameters. Unspecified or null parameters are not included. Use cases: - Modify trigger price - Modify trade amount - Modify trigger direction - Modify task enabled status Example: { "id": "lmn27zhq065q3u", "enabled": true, "triggerPriceUsd": "10.5", "triggerDirection": "up", "currencyAmountUI": 1, " } `, inputSchema: { type: 'object', properties: { id: { type: 'string', description: 'Limit order ID' }, enabled: { type: 'boolean', description: 'Task enabled status, true to enable, false to disable' }, groupId: { type: 'string', description: 'Group ID for order management' }, triggerPriceUsd: { type: 'string', description: 'Price in USD that triggers buy/sell' }, triggerDirection: { type: 'string', enum: ['up', 'down'], description: '"down" executes buy/sell when price falls below trigger, "up" executes buy/sell when price rises above trigger' }, currencyAmountUI: { type: 'number', description: 'For buy orders: amount in ETH/SOL/BNB/TRX. For sell orders: proportion to sell (0.00-1.00)' }, customFeeAndTip: { type: 'boolean', description: '"true" means both priority fee and tip are valid, system will execute with provided values (null means auto priority fee/auto tip), "false" means only priority fee in high speed mode and only tip in anti-sandwich mode' }, priorityFee: { type: 'string', description: 'Priority fee (SOL), valid for Solana, empty string means auto priority fee' }, gasFeeDelta: { type: 'number', description: 'Additional gas (Gwei), valid for EVM chains' }, maxFeePerGas: { type: 'number', description: 'Transaction will not execute if base gas exceeds this value (Gwei), valid for EVM chains' }, jitoEnabled: { type: 'boolean', description: '"true" enables anti-sandwich mode (Solana & Ethereum & Bsc)' }, jitoTip: { type: 'number', description: 'Tip used in anti-sandwich mode (Solana)' }, expireDelta: { type: 'number', description: 'Task validity duration, maximum 432000000 (milliseconds)' }, expireExecute: { type: 'boolean', description: '"true" means if task has not triggered by expiry, will buy/sell tokens at current market price' }, useMidPrice: { type: 'boolean', description: '"true" enables anti-spike mode, uses 1-second median price as trigger price, helps avoid but cannot completely prevent price spikes' }, maxSlippage: { type: 'number', description: 'Maximum slippage (0.00-1.00). For buys: difference between actual and expected price. For sells: difference between expected and actual price. Multiple = 1/(1-slippage), 0.5 means accept up to 2x price difference, 1 means no price limit' }, concurrentNodes: { type: 'number', description: 'Number of concurrent nodes (1-3)' }, retries: { type: 'number', description: 'Number of retries on failure (0-10)' } }, required: ['id'], }, }, { name: 'switch_limit_order', description: `Enable/disable a specified limit order. Can be used to pause or resume execution of a limit order. Use cases: - Temporarily pause a limit order - Re-enable a disabled limit order - Quickly control order status without deleting the order Example: { "id": "lmn27zhq065q3u", "enabled": false } `, inputSchema: { type: 'object', properties: { id: { type: 'string', description: 'ID of limit order to switch status', }, enabled: { type: 'boolean', description: 'Target status: true=enable order execution, false=disable order execution', }, }, required: ['id', 'enabled'], }, }, { name: 'delete_limit_order', description: 'Delete a specific limit order', inputSchema: { type: 'object', properties: { id: { type: 'string', description: 'Limit order ID', }, }, required: ['id'], }, }, { name: 'delete_limit_orders', description: 'Batch delete specified limit orders', inputSchema: { type: 'object', properties: { ids: { type: 'array', items: { type: 'string' }, description: 'Array of limit order IDs', }, }, required: ['ids'], }, }, { name: 'delete_all_limit_order', description: 'Delete all limit orders (regardless of in progress/completed/expired)', inputSchema: { type: 'object', properties: { source: { type: 'string', enum: ['normal', 'pnl_for_follow', 'pnl_for_swap'], default: 'normal', description: 'Type of limit orders to delete: normal-manually created, pnl_for_follow-created by following, pnl_for_swap-created by quick swap (default: normal)', }, }, }, }, { name: 'limit_orders', description: 'Get all limit orders for user.', inputSchema: { type: 'object', properties: { page: { type: 'number', description: 'Page number', default: 0, }, size: { type: 'number', description: 'Items per page (max 20)', default: 10, maximum: 20, }, chain: { type: 'string', enum: ['solana', 'ethereum', 'base', 'bsc', 'tron'], description: 'Optional: Filter limit orders by chain', }, pair: { type: 'string', description: 'Optional: Filter limit orders by token', }, state: { type: 'string', enum: ['init', 'done', 'expired', 'canceled'], description: 'Optional: Filter limit orders by state', }, enabled: { type: 'boolean', description: 'Optional: Filter enabled/disabled limit orders', }, }, }, }, ], }; }); this.server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { switch (name) { case 'create_limit_order': return await this.handleCreateLimitOrder(args); case 'edit_limit_order': return await this.handleEditLimitOrder(args); case 'switch_limit_order': return await this.handleSwitchLimitOrder(args); case 'delete_limit_order': return await this.handleDeleteLimitOrder(args); case 'delete_limit_orders': return await this.handleDeleteLimitOrders(args); case 'delete_all_limit_order': return await this.handleDeleteAllLimitOrders(args); case 'limit_orders': return await this.handleLimitOrders(args); default: throw new types_js_1.McpError(types_js_1.ErrorCode.MethodNotFound, `Unknown tool: ${name}`); } } catch (error) { if (error instanceof types_js_1.McpError) { throw error; } const errorMessage = error instanceof Error ? error.message : String(error); throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Error executing tool ${name}: ${errorMessage}`); } }); } async handleCreateLimitOrder(args) { // Use environment variable if walletId not provided if (!args.walletId) { args.walletId = process.env.DBOT_WALLET_ID; if (!args.walletId) { throw new Error('Wallet ID not provided: Please specify walletId in parameters or set DBOT_WALLET_ID environment variable'); } } const validatedArgs = types_js_2.CreateLimitOrderRequestSchema.parse(args); try { const response = await this.client.createLimitOrder(validatedArgs); if (response.err) { return { content: [ { type: 'text', text: `❌ Failed to create limit order: API returned error\nDocs: ${response.docs}`, }, ], }; } const orderIds = response.res?.ids || []; const idsText = orderIds.length > 0 ? orderIds.join(', ') : 'Unknown'; // Generate details for each limit order const settingsInfo = validatedArgs.settings.map((setting, index) => { const orderId = orderIds[index] || 'Unknown'; return `Order ID: ${orderId}\n` + `- Trade Type: ${setting.tradeType}\n` + `- Trigger Price: $${setting.triggerPriceUsd}\n` + `- Trigger Direction: ${setting.triggerDirection}\n` + `- Trade Amount: ${setting.currencyAmountUI}\n` + `- Status: ${setting.enabled ? 'Enabled' : 'Disabled'}`; }).join('\n\n'); return { content: [ { type: 'text', text: `✅ Limit orders created successfully!\n\nCreated ${validatedArgs.settings.length} limit orders\nChain: ${validatedArgs.chain}\nToken: ${validatedArgs.pair}\n\n${settingsInfo}\n\nDocs: ${response.docs}`, }, ], }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { content: [ { type: 'text', text: `❌ Error creating limit order: ${errorMessage}`, }, ], }; } } async handleEditLimitOrder(args) { const validatedArgs = types_js_2.EditLimitOrderRequestSchema.parse(args); try { const response = await this.client.editLimitOrder(validatedArgs); if (response.err) { return { content: [ { type: 'text', text: `❌ Failed to edit limit order: API returned error\nDocs: ${response.docs}`, }, ], }; } return { content: [ { type: 'text', text: `✅ Limit order edited successfully!\n\nOrder ID: ${validatedArgs.id}\n\nDocs: ${response.docs}`, }, ], }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { content: [ { type: 'text', text: `❌ Error editing limit order: ${errorMessage}`, }, ], }; } } async handleSwitchLimitOrder(args) { const validatedArgs = types_js_2.SwitchLimitOrderRequestSchema.parse(args); try { const response = await this.client.switchLimitOrder(validatedArgs); if (response.err) { return { content: [ { type: 'text', text: `❌ Failed to switch limit order status: API returned error\nDocs: ${response.docs}`, }, ], }; } const status = validatedArgs.enabled ? 'Enabled' : 'Disabled'; return { content: [ { type: 'text', text: `✅ Limit order status switched successfully!\n\nOrder ID: ${validatedArgs.id}\nNew Status: ${status}\n\nDocs: ${response.docs}`, }, ], }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { content: [ { type: 'text', text: `❌ Error switching limit order status: ${errorMessage}`, }, ], }; } } async handleDeleteLimitOrder(args) { const validatedArgs = types_js_2.DeleteLimitOrderRequestSchema.parse(args); try { const response = await this.client.deleteLimitOrder(validatedArgs); if (response.err) { return { content: [ { type: 'text', text: `❌ Failed to delete limit order: API returned error\nDocs: ${response.docs}`, }, ], }; } return { content: [ { type: 'text', text: `✅ Limit order deleted successfully!\n\nOrder ID: ${validatedArgs.id}\n\nDocs: ${response.docs}`, }, ], }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { content: [ { type: 'text', text: `❌ Error deleting limit order: ${errorMessage}`, }, ], }; } } async handleDeleteLimitOrders(args) { const validatedArgs = types_js_2.DeleteLimitOrdersRequestSchema.parse(args); try { const response = await this.client.deleteLimitOrders(validatedArgs); if (response.err) { return { content: [ { type: 'text', text: `❌ Failed to batch delete limit orders: API returned error\nDocs: ${response.docs}`, }, ], }; } return { content: [ { type: 'text', text: `✅ Batch delete limit orders successful!\n\nOrder IDs: ${validatedArgs.ids.join(', ')}\n\nDocs: ${response.docs}`, }, ], }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { content: [ { type: 'text', text: `❌ Error batch deleting limit orders: ${errorMessage}`, }, ], }; } } async handleDeleteAllLimitOrders(args) { // Set default value if (!args.source) { args.source = 'normal'; } const validatedArgs = types_js_2.DeleteAllLimitOrderRequestSchema.parse(args); try { const response = await this.client.deleteAllLimitOrders(validatedArgs); if (response.err) { return { content: [ { type: 'text', text: `❌ Failed to delete all limit orders: API returned error\nDocs: ${response.docs}`, }, ], }; } const sourceMap = { 'normal': 'Manually created limit orders', 'pnl_for_follow': 'Take profit/stop loss orders created by following', 'pnl_for_swap': 'Take profit/stop loss orders created by quick swap' }; return { content: [ { type: 'text', text: `✅ Delete limit orders successful!\n\nDeleted Type: ${sourceMap[validatedArgs.source]}\n\nDocs: ${response.docs}`, }, ], }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { content: [ { type: 'text', text: `❌ Error deleting all limit orders: ${errorMessage}`, }, ], }; } } async handleLimitOrders(args) { const validatedArgs = types_js_2.LimitOrdersRequestSchema.parse(args); try { const response = await this.client.getLimitOrders(validatedArgs); if (response.err) { return { content: [ { type: 'text', text: `❌ Failed to query limit orders: API returned error\nDocs: ${response.docs}`, }, ], }; } const orders = response.res || []; let result = `📊 Limit Orders List (${orders.length} orders total):\n\n`; orders.forEach((order, index) => { result += `${index + 1}. Order ID: ${order.id}\n`; result += ` Chain: ${order.chain}\n`; result += ` Trading Pair: ${order.pair}\n`; result += ` Pair Type: ${order.pairType}\n`; result += ` Trade Type: ${order.tradeType}\n`; result += ` State: ${order.state}\n`; result += ` Enabled: ${order.enabled ? 'Yes' : 'No'}\n`; result += ` Trigger Price: $${order.triggerPriceUsd}\n`; result += ` Trigger Direction: ${order.triggerDirection}\n`; result += ` Amount: ${order.currencyAmountUI}\n`; result += ` Wallet: ${order.walletName} (${order.walletAddress})\n`; result += ` Group ID: ${order.groupId}\n`; result += ` Expiry Time: ${new Date(order.expireAt).toLocaleString()}\n`; result += ` Max Slippage: ${(order.maxSlippage * 100).toFixed(1)}%\n`; result += ` Jito Enabled: ${order.jitoEnabled ? 'Yes' : 'No'}\n`; if (order.jitoEnabled) { result += ` Jito Fee: ${order.jitoTip} SOL\n`; } if (order.errorMessage) { result += ` Error Message: ${order.errorMessage}\n`; } if (order.tokenInfo) { result += ` Token Name: ${order.tokenInfo.name} (${order.tokenInfo.symbol})\n`; } result += '\n'; }); if (orders.length === 0) { result += 'No limit orders\n\n'; } result += `Docs: ${response.docs}`; return { content: [ { type: 'text', text: result, }, ], }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { content: [ { type: 'text', text: `❌ Error querying limit orders: ${errorMessage}`, }, ], }; } } async run() { const transport = new stdio_js_1.StdioServerTransport(); await this.server.connect(transport); console.error('Limit Order MCP Server Started'); } } async function main() { const server = new LimitOrderMcpServer(); await server.run(); } // @ts-ignore if (require.main === module) { main().catch((error) => { console.error('Fatal error in main():', error); process.exit(1); }); } //# sourceMappingURL=index.js.map