# Directory Structure
```
├── .dockerignore
├── .gitignore
├── .npmrc
├── Dockerfile
├── package-lock.json
├── package.json
├── pnpm-lock.yaml
├── README_CN.md
├── README.md
├── smithery.yaml
├── src
│ ├── index.ts
│ ├── models
│ │ └── db.ts
│ ├── services
│ │ ├── binance.ts
│ │ ├── binanceFutures.ts
│ │ ├── keystore.ts
│ │ └── tools.ts
│ └── types
│ ├── binance-connector.d.ts
│ ├── binance.ts
│ ├── errors.ts
│ └── futures.ts
├── start-bn-service.sh
└── tsconfig.json
```
# Files
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
1 | node_modules/
2 | build/
3 | *.log
4 | .env*
5 | data/*
6 | .vscode/*
7 | .env
```
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
```
1 | node_modules
2 | npm-debug.log
3 | build
4 | .env
5 | .git
6 | .gitignore
7 | README.md
8 | .DS_Store
```
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
```
1 | enable-pre-post-scripts=true
2 | unsafe-perm=true
3 | node-linker=hoisted
4 | ignore-scripts=false
5 | public-hoist-pattern[]=*
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
1 | # mcp-server-cex-bn
2 |
3 | [](https://smithery.ai/server/mcp-server-cex-bn)
4 |
5 | This MCP Server provides comprehensive integration with Binance's spot and futures trading operations.
6 |
7 | [中文说明](README_CN.md)
8 |
9 | ## Features
10 |
11 | ### Spot Trading Operations
12 | - Execute spot trading operations (LIMIT/MARKET orders)
13 | - Monitor account balances
14 | - Track and manage open orders
15 | - Cancel existing orders
16 |
17 | ### Futures Trading Operations
18 | - Create various types of futures orders (LIMIT, MARKET, STOP, TAKE_PROFIT, etc.)
19 | - Manage leverage settings (1-125x)
20 | - Monitor futures positions and account information
21 | - Track funding rates
22 | - Support for both one-way and hedge mode positions
23 | - Advanced order types including trailing stops and reduce-only orders
24 |
25 | ### Tools
26 |
27 | #### API Configuration
28 |
29 | ##### `configure_api_keys`
30 | Securely store your Binance API credentials:
31 | ```typescript
32 | await configureBinanceApiKeys({
33 | apiKey: 'your-api-key',
34 | apiSecret: 'your-api-secret'
35 | });
36 | ```
37 |
38 | #### Spot Trading Tools
39 |
40 | ##### `create_spot_order`
41 | Create LIMIT or MARKET orders:
42 | ```typescript
43 | // LIMIT order
44 | await createSpotOrder({
45 | symbol: 'BTCUSDT',
46 | side: 'BUY',
47 | type: 'LIMIT',
48 | quantity: '0.001',
49 | price: '40000'
50 | });
51 |
52 | // MARKET order
53 | await createSpotOrder({
54 | symbol: 'BTCUSDT',
55 | side: 'BUY',
56 | type: 'MARKET',
57 | quantity: '0.001'
58 | });
59 | ```
60 |
61 | ##### `cancel_order`
62 | Cancel an existing order:
63 | ```typescript
64 | await cancelOrder({
65 | symbol: 'BTCUSDT',
66 | orderId: '12345678'
67 | });
68 | ```
69 |
70 | ##### `get_balances`
71 | Check your account balances:
72 | ```typescript
73 | const balances = await getBalances();
74 | // Returns: { BTC: '0.1', USDT: '1000', ... }
75 | ```
76 |
77 | ##### `get_open_orders`
78 | List all open orders:
79 | ```typescript
80 | const orders = await getOpenOrders({
81 | symbol: 'BTCUSDT' // Optional: specify symbol
82 | });
83 | ```
84 |
85 | #### Futures Trading Tools
86 |
87 | ##### `create_futures_order`
88 | Create various types of futures orders:
89 | ```typescript
90 | // LIMIT order
91 | await createFuturesOrder({
92 | symbol: 'BTCUSDT',
93 | side: 'BUY',
94 | type: 'LIMIT',
95 | quantity: '0.001',
96 | price: '40000',
97 | timeInForce: 'GTC'
98 | });
99 |
100 | // STOP MARKET order
101 | await createFuturesOrder({
102 | symbol: 'BTCUSDT',
103 | side: 'SELL',
104 | type: 'STOP_MARKET',
105 | quantity: '0.001',
106 | stopPrice: '38000'
107 | });
108 |
109 | // TRAILING STOP order
110 | await createFuturesOrder({
111 | symbol: 'BTCUSDT',
112 | side: 'SELL',
113 | type: 'TRAILING_STOP_MARKET',
114 | quantity: '0.001',
115 | callbackRate: '1.0' // 1% callback rate
116 | });
117 | ```
118 |
119 | ##### `set_futures_leverage`
120 | Adjust leverage for a trading pair:
121 | ```typescript
122 | await setFuturesLeverage({
123 | symbol: 'BTCUSDT',
124 | leverage: 10 // 1-125x
125 | });
126 | ```
127 |
128 | ##### `get_futures_positions`
129 | Get all open futures positions:
130 | ```typescript
131 | const positions = await getFuturesPositions();
132 | ```
133 |
134 | ##### `get_futures_account`
135 | Get detailed futures account information:
136 | ```typescript
137 | const account = await getFuturesAccount();
138 | ```
139 |
140 | ##### `get_funding_rate`
141 | Get funding rate for a futures symbol:
142 | ```typescript
143 | const fundingRate = await getFundingRate({
144 | symbol: 'BTCUSDT'
145 | });
146 | ```
147 |
148 | ##### `cancel_futures_order`
149 | Cancel an existing futures order:
150 | ```typescript
151 | await cancelFuturesOrder({
152 | symbol: 'BTCUSDT',
153 | orderId: '12345678'
154 | });
155 | ```
156 |
157 | ## Futures Trading Details
158 |
159 | ### Position Modes
160 | - One-way Mode: Single position per symbol
161 | * Default mode, simpler position management
162 | * Total position size is the sum of all orders
163 | - Hedge Mode: Separate long and short positions
164 | * Allows holding both long and short positions simultaneously
165 | * Each position has independent margin requirements
166 |
167 | ### Margin Types
168 | - Isolated Margin: Fixed margin per position
169 | * Risk is limited to the allocated margin
170 | * Each position has its own leverage setting
171 | - Cross Margin: Shared margin across positions
172 | * Higher capital efficiency
173 | * Shared risk across all positions
174 |
175 | ### Funding Rate
176 | Perpetual futures contracts use funding rates to keep futures prices aligned with spot prices:
177 | - Positive rate: Longs pay shorts
178 | - Negative rate: Shorts pay longs
179 | - Payments occur every 8 hours
180 |
181 | ## Security Considerations
182 |
183 | ### Spot Trading Security
184 | - Never commit API keys to version control
185 | - Use environment variables or secure key storage
186 | - Restrict API key permissions to only required operations
187 | - Regularly rotate your API keys
188 |
189 | ### Futures Trading Security
190 | - Set appropriate leverage limits based on risk tolerance
191 | - Always use stop-loss orders to limit potential losses
192 | - Monitor liquidation prices carefully
193 | - Regularly check position risks and margin ratios
194 | - Consider using reduce-only orders for risk management
195 | - Be cautious with cross-margin due to shared risk
196 |
197 | ## Rate Limits
198 |
199 | - Respect Binance API rate limits
200 | - Default rate limits:
201 | - 1200 requests per minute for order operations
202 | - 100 requests per second for market data
203 | - Implement proper error handling for rate limit errors
204 |
205 | ## Error Handling
206 |
207 | ### Common Error Scenarios
208 | - Invalid API credentials
209 | - Insufficient balance or margin
210 | - Invalid order parameters
211 | - Rate limit exceeded
212 | - Network connectivity issues
213 |
214 | ### Futures-Specific Errors
215 | - InsufficientMarginError: Not enough margin for operation
216 | - InvalidPositionModeError: Wrong position mode setting
217 | - OrderValidationError: Invalid futures order parameters
218 |
219 | Example error handling:
220 | ```typescript
221 | try {
222 | await createFuturesOrder({
223 | symbol: 'BTCUSDT',
224 | side: 'BUY',
225 | type: 'LIMIT',
226 | quantity: '0.001',
227 | price: '40000',
228 | timeInForce: 'GTC'
229 | });
230 | } catch (error) {
231 | if (error instanceof InsufficientMarginError) {
232 | console.error('Insufficient margin available');
233 | } else if (error instanceof InvalidPositionModeError) {
234 | console.error('Invalid position mode');
235 | } else if (error instanceof OrderValidationError) {
236 | console.error('Invalid order parameters');
237 | }
238 | }
239 | ```
240 |
241 | ## Project Structure
242 |
243 | ```
244 | .
245 | ├── src/
246 | │ ├── index.ts # Server entry point
247 | │ ├── services/
248 | │ │ ├── binance.ts # Binance API integration
249 | │ │ ├── keystore.ts # API key management
250 | │ │ └── tools.ts # Trading tools implementation
251 | │ └── types/
252 | │ ├── binance.ts # Binance types
253 | │ └── binance-connector.d.ts # API client types
254 | ├── README.md
255 | ├── README_CN.md
256 | ├── package.json
257 | ├── pnpm-lock.yaml
258 | └── tsconfig.json
259 | ```
260 |
261 | ## Development
262 |
263 | 1. Set up environment variables:
264 |
265 | create `.env` file in the root directory, and set your Binance API credentials:
266 |
267 | ```txt
268 | BINANCE_API_KEY=your_api_key_here
269 | BINANCE_API_SECRET=your_secret_key_here
270 | ```
271 |
272 | 2. Install dependencies:
273 |
274 | ```bash
275 | pnpm install
276 | ```
277 |
278 | Build the server:
279 |
280 | ```bash
281 | pnpm build
282 | ```
283 |
284 | For development with auto-rebuild:
285 |
286 | ```bash
287 | pnpm watch
288 | ```
289 |
290 | ## Installation
291 |
292 | ### Installing via Smithery
293 |
294 | To install Binance Trading Server for Claude Desktop automatically via [Smithery](https://smithery.ai/server/mcp-server-cex-bn):
295 |
296 | ```bash
297 | npx -y @smithery/cli install mcp-server-cex-bn --client claude
298 | ```
299 |
300 | ### Installing manually
301 | 1. Clone the repository
302 | 2. Install dependencies:
303 | ```bash
304 | pnpm install
305 | ```
306 | 3. Configure your Binance API credentials in `.env`
307 | 4. Build and start the server:
308 | ```bash
309 | pnpm build
310 | pnpm start
311 | ```
312 |
313 | ### Debugging
314 |
315 | Since MCP servers communicate over stdio, debugging can be challenging. We recommend using the [MCP Inspector](https://github.com/modelcontextprotocol/inspector), which is available as a package script:
316 |
317 | ```bash
318 | pnpm inspector
319 | ```
320 |
321 | The Inspector will provide a URL to access debugging tools in your browser.
322 |
323 |
324 | # mcp-server-bn
325 |
```
--------------------------------------------------------------------------------
/start-bn-service.sh:
--------------------------------------------------------------------------------
```bash
1 | #!/bin/bash
2 | cd "$(dirname "$0")"
3 | export BINANCE_API_KEY="rLV9ffU8oyqanh5znqit08j8CnILLvDCkRgcxdx2ePdVBxmxSYqgvSNeybyx2mtL"
4 | export BINANCE_API_SECRET="ukawP66HgMDvLnSJOYAk7qkJEbdIvlcDwQuc2WxoD7E0FZJ5TupKhP4WsCbAeECd"
5 | node build/index.js
6 |
```
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "compilerOptions": {
3 | "target": "ES2022",
4 | "module": "Node16",
5 | "moduleResolution": "Node16",
6 | "outDir": "./build",
7 | "rootDir": "./src",
8 | "strict": true,
9 | "esModuleInterop": true,
10 | "skipLibCheck": true,
11 | "forceConsistentCasingInFileNames": true
12 | },
13 | "include": ["src/**/*"],
14 | "exclude": ["node_modules"]
15 | }
16 |
```
--------------------------------------------------------------------------------
/src/models/db.ts:
--------------------------------------------------------------------------------
```typescript
1 | import sqlite3 from "sqlite3";
2 |
3 | export function getDb(): sqlite3.Database {
4 | const dbName = process.env.BINANCE_DB_PATH || "";
5 | if (!dbName) {
6 | throw new Error("BINANCE_DB_PATH is not set");
7 | }
8 |
9 | const db = new sqlite3.Database(dbName, (err) => {
10 | if (err) {
11 | console.error("binance db connect failed: ", dbName, err.message);
12 | return;
13 | }
14 | });
15 |
16 | return db;
17 | }
18 |
```
--------------------------------------------------------------------------------
/src/types/errors.ts:
--------------------------------------------------------------------------------
```typescript
1 | export class BinanceClientError extends Error {
2 | constructor(message: string) {
3 | super(message);
4 | this.name = 'BinanceClientError';
5 | }
6 | }
7 |
8 | export class ApiKeyError extends Error {
9 | constructor(message: string) {
10 | super(message);
11 | this.name = 'ApiKeyError';
12 | }
13 | }
14 |
15 | export class OrderValidationError extends Error {
16 | constructor(message: string) {
17 | super(message);
18 | this.name = 'OrderValidationError';
19 | }
20 | }
21 |
22 | export class InsufficientMarginError extends Error {
23 | constructor(message: string) {
24 | super(message);
25 | this.name = 'InsufficientMarginError';
26 | }
27 | }
28 |
29 | export class InvalidPositionModeError extends Error {
30 | constructor(message: string) {
31 | super(message);
32 | this.name = 'InvalidPositionModeError';
33 | }
34 | }
35 |
```
--------------------------------------------------------------------------------
/smithery.yaml:
--------------------------------------------------------------------------------
```yaml
1 | # Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml
2 |
3 | startCommand:
4 | type: stdio
5 | configSchema:
6 | # JSON Schema defining the configuration options for the MCP.
7 | type: object
8 | required:
9 | - binanceApiKey
10 | - binanceApiSecret
11 | properties:
12 | binanceApiKey:
13 | type: string
14 | description: The API key for accessing the Binance API.
15 | binanceApiSecret:
16 | type: string
17 | description: The secret key for accessing the Binance API.
18 | commandFunction:
19 | # A function that produces the CLI command to start the MCP on stdio.
20 | |-
21 | (config) => ({ command: 'pnpm', args: ['start'], env: { BINANCE_API_KEY: config.binanceApiKey, BINANCE_API_SECRET: config.binanceApiSecret } })
```
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
```dockerfile
1 | # Build stage
2 | FROM node:20-alpine AS builder
3 |
4 | # Set the working directory
5 | WORKDIR /app
6 |
7 | # Install build dependencies
8 | RUN apk add --no-cache python3 make g++ git
9 |
10 | # Install pnpm
11 | RUN npm install -g pnpm@latest typescript
12 |
13 | # Copy package files
14 | COPY package.json pnpm-lock.yaml ./
15 |
16 | # Install dependencies
17 | RUN pnpm install --frozen-lockfile --ignore-scripts
18 |
19 | # Copy source files
20 | COPY . .
21 |
22 | # Build TypeScript
23 | RUN pnpm exec tsc
24 |
25 | # Production stage
26 | FROM node:20-alpine
27 |
28 | WORKDIR /app
29 |
30 | # Install runtime dependencies
31 | RUN apk add --no-cache python3 make g++
32 |
33 | # Copy package files and install production dependencies
34 | COPY package.json pnpm-lock.yaml ./
35 | RUN npm install -g pnpm@latest && \
36 | pnpm install --prod --frozen-lockfile --ignore-scripts
37 |
38 | # Copy built files
39 | COPY --from=builder /app/build ./build
40 |
41 | # Set environment variables
42 | ENV NODE_ENV=production
43 |
44 | # Start the application
45 | CMD ["node", "build/index.js"]
```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "name": "@kydfeng/mcp-server-cex-bn",
3 | "version": "0.1.2",
4 | "description": "MCP Server for Binance Spot Trading",
5 | "type": "module",
6 | "bin": {
7 | "mcp-server-cex-bn": "./build/index.js"
8 | },
9 | "files": [
10 | "build"
11 | ],
12 | "scripts": {
13 | "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
14 | "prepare": "npm run build",
15 | "watch": "tsc --watch",
16 | "inspector": "npx @modelcontextprotocol/inspector build/index.js"
17 | },
18 | "dependencies": {
19 | "@binance/connector": "^3.6.1",
20 | "@modelcontextprotocol/inspector": "^0.4.1",
21 | "@modelcontextprotocol/sdk": "1.6.0",
22 | "@types/keytar": "^4.4.2",
23 | "dotenv": "^16.4.6",
24 | "keytar": "^7.9.0",
25 | "node-binance-api": "^0.13.1",
26 | "sqlite3": "^5.1.7"
27 | },
28 | "devDependencies": {
29 | "@types/node": "^20.17.9",
30 | "typescript": "^5.3.3"
31 | },
32 | "keywords": [
33 | "mcp",
34 | "binance",
35 | "trading",
36 | "crypto",
37 | "model-context-protocol"
38 | ],
39 | "author": "kydfeng",
40 | "license": "MIT",
41 | "repository": {
42 | "type": "git",
43 | "url": "https://github.com/kydfeng/mcp-server-bn.git"
44 | }
45 | }
46 |
```
--------------------------------------------------------------------------------
/src/types/binance-connector.d.ts:
--------------------------------------------------------------------------------
```typescript
1 | declare module '@binance/connector' {
2 | export class Spot {
3 | constructor(apiKey: string, apiSecret: string);
4 |
5 | usdmFutures: {
6 | ping(): Promise<any>;
7 | account(): Promise<any>;
8 | positionRisk(): Promise<any>;
9 | openOrders(params?: { symbol?: string }): Promise<any>;
10 | newOrder(params: any): Promise<any>;
11 | cancelOrder(params: any): Promise<any>;
12 | leverage(params: any): Promise<any>;
13 | fundingRate(params: any): Promise<any>;
14 | };
15 |
16 | newOrder(params: {
17 | symbol: string;
18 | side: 'BUY' | 'SELL';
19 | type: 'LIMIT' | 'MARKET';
20 | quantity?: string;
21 | price?: string;
22 | timeInForce?: 'GTC' | 'IOC' | 'FOK';
23 | }): Promise<{ data: any }>;
24 |
25 | cancelOrder(symbol: string, params: { orderId: number }): Promise<void>;
26 |
27 | account(): Promise<{
28 | data: {
29 | balances: Array<{
30 | asset: string;
31 | free: string;
32 | locked: string;
33 | }>;
34 | };
35 | }>;
36 |
37 | openOrders(params?: { symbol?: string }): Promise<{
38 | data: Array<{
39 | symbol: string;
40 | orderId: number;
41 | orderListId: number;
42 | clientOrderId: string;
43 | price: string;
44 | origQty: string;
45 | executedQty: string;
46 | status: string;
47 | timeInForce: string;
48 | type: string;
49 | side: string;
50 | }>;
51 | }>;
52 | }
53 | }
54 |
```
--------------------------------------------------------------------------------
/src/services/keystore.ts:
--------------------------------------------------------------------------------
```typescript
1 | import dotenv from 'dotenv';
2 | import * as fs from 'fs';
3 | import * as path from 'path';
4 |
5 | // Load environment variables
6 | dotenv.config();
7 |
8 | const logFile = path.join(process.cwd(), 'logs', 'keystore.log');
9 |
10 | // 确保日志目录存在
11 | if (!fs.existsSync(path.dirname(logFile))) {
12 | fs.mkdirSync(path.dirname(logFile), { recursive: true });
13 | }
14 |
15 | function log(message: string) {
16 | const timestamp = new Date().toISOString();
17 | fs.appendFileSync(logFile, `${timestamp} - ${message}\n`);
18 | }
19 |
20 | function logError(message: string, error?: unknown) {
21 | const timestamp = new Date().toISOString();
22 | const errorMessage = error instanceof Error ? error.message : String(error);
23 | fs.appendFileSync(logFile, `${timestamp} - ERROR: ${message} ${error ? `- ${errorMessage}` : ''}\n`);
24 | }
25 |
26 | export async function storeApiKeys(apiKey: string, apiSecret: string): Promise<void> {
27 | process.env.BINANCE_API_KEY = apiKey;
28 | process.env.BINANCE_API_SECRET = apiSecret;
29 | log('API keys stored in environment variables');
30 | }
31 |
32 | export async function getApiKeys(): Promise<{ apiKey: string; apiSecret: string } | null> {
33 | const envApiKey = process.env.BINANCE_API_KEY;
34 | const envApiSecret = process.env.BINANCE_API_SECRET;
35 |
36 | if (envApiKey && envApiSecret) {
37 | log('Found API keys in environment variables');
38 | return { apiKey: envApiKey, apiSecret: envApiSecret };
39 | }
40 |
41 | log('No API keys found in environment variables');
42 | return null;
43 | }
44 |
45 | export async function deleteApiKeys(): Promise<void> {
46 | delete process.env.BINANCE_API_KEY;
47 | delete process.env.BINANCE_API_SECRET;
48 | log('Cleared API keys from environment variables');
49 | }
50 |
```
--------------------------------------------------------------------------------
/src/types/binance.ts:
--------------------------------------------------------------------------------
```typescript
1 | export interface BinanceCredentials {
2 | apiKey: string;
3 | apiSecret: string;
4 | }
5 |
6 | export interface SpotOrder {
7 | symbol: string;
8 | side: 'BUY' | 'SELL';
9 | type: 'LIMIT' | 'MARKET';
10 | quantity?: string;
11 | quoteOrderQty?: string;
12 | price?: string;
13 | timeInForce?: 'GTC' | 'IOC' | 'FOK';
14 | }
15 |
16 | export interface OrderResponse {
17 | symbol: string;
18 | orderId: number;
19 | orderListId: number;
20 | clientOrderId: string;
21 | transactTime: number;
22 | price: string;
23 | origQty: string;
24 | executedQty: string;
25 | status: string;
26 | timeInForce: string;
27 | type: string;
28 | side: string;
29 | }
30 |
31 | export interface AccountBalance {
32 | asset: string;
33 | free: string;
34 | locked: string;
35 | }
36 |
37 | export interface SymbolFilter {
38 | filterType: string;
39 | minPrice?: string;
40 | maxPrice?: string;
41 | tickSize?: string;
42 | minQty?: string;
43 | maxQty?: string;
44 | stepSize?: string;
45 | minNotional?: string;
46 | applyToMarket?: boolean;
47 | limit?: number;
48 | maxNumOrders?: number;
49 | maxNumAlgoOrders?: number;
50 | }
51 |
52 | export interface SymbolInfo {
53 | symbol: string;
54 | status: string;
55 | baseAsset: string;
56 | baseAssetPrecision: number;
57 | quoteAsset: string;
58 | quotePrecision: number;
59 | quoteAssetPrecision: number;
60 | orderTypes: string[];
61 | icebergAllowed: boolean;
62 | ocoAllowed: boolean;
63 | isSpotTradingAllowed: boolean;
64 | isMarginTradingAllowed: boolean;
65 | filters: SymbolFilter[];
66 | permissions: string[];
67 | }
68 |
69 | export interface ExchangeInfo {
70 | timezone: string;
71 | serverTime: number;
72 | rateLimits: any[];
73 | exchangeFilters: any[];
74 | symbols: SymbolInfo[];
75 | }
76 |
77 | export interface KlineData {
78 | openTime: number;
79 | open: string;
80 | high: string;
81 | low: string;
82 | close: string;
83 | volume: string;
84 | closeTime: number;
85 | quoteAssetVolume: string;
86 | trades: number;
87 | takerBuyBaseAssetVolume: string;
88 | takerBuyQuoteAssetVolume: string;
89 | }
90 |
```
--------------------------------------------------------------------------------
/src/types/futures.ts:
--------------------------------------------------------------------------------
```typescript
1 | export enum PositionSide {
2 | BOTH = 'BOTH',
3 | LONG = 'LONG',
4 | SHORT = 'SHORT'
5 | }
6 |
7 | export enum MarginType {
8 | ISOLATED = 'ISOLATED',
9 | CROSSED = 'CROSSED'
10 | }
11 |
12 | export enum WorkingType {
13 | MARK_PRICE = 'MARK_PRICE',
14 | CONTRACT_PRICE = 'CONTRACT_PRICE'
15 | }
16 |
17 | export enum TimeInForce {
18 | GTC = 'GTC', // Good Till Cancel
19 | IOC = 'IOC', // Immediate or Cancel
20 | FOK = 'FOK', // Fill or Kill
21 | GTX = 'GTX' // Good Till Crossing
22 | }
23 |
24 | export interface FuturesOrder {
25 | symbol: string;
26 | side: 'BUY' | 'SELL';
27 | positionSide?: PositionSide;
28 | type: 'LIMIT' | 'MARKET' | 'STOP' | 'STOP_MARKET' | 'TAKE_PROFIT' | 'TAKE_PROFIT_MARKET' | 'TRAILING_STOP_MARKET';
29 | quantity: string;
30 | price?: string;
31 | stopPrice?: string;
32 | reduceOnly?: boolean;
33 | workingType?: WorkingType;
34 | timeInForce?: TimeInForce;
35 | activationPrice?: string;
36 | callbackRate?: string;
37 | closePosition?: boolean;
38 | priceProtect?: boolean;
39 | }
40 |
41 | export interface FuturesPosition {
42 | symbol: string;
43 | positionAmt: string;
44 | entryPrice: string;
45 | markPrice: string;
46 | unRealizedProfit: string;
47 | liquidationPrice: string;
48 | leverage: number;
49 | marginType: MarginType;
50 | isolatedMargin: string;
51 | positionSide: PositionSide;
52 | updateTime: number;
53 | }
54 |
55 | export interface FundingRate {
56 | symbol: string;
57 | fundingRate: string;
58 | fundingTime: number;
59 | nextFundingTime: number;
60 | }
61 |
62 | export interface LeverageSettings {
63 | symbol: string;
64 | leverage: number;
65 | maxNotionalValue?: string;
66 | }
67 |
68 | export interface FuturesAccountBalance {
69 | asset: string;
70 | walletBalance: string;
71 | unrealizedProfit: string;
72 | marginBalance: string;
73 | maintMargin: string;
74 | initialMargin: string;
75 | positionInitialMargin: string;
76 | openOrderInitialMargin: string;
77 | maxWithdrawAmount: string;
78 | crossWalletBalance: string;
79 | crossUnPnl: string;
80 | availableBalance: string;
81 | }
82 |
83 | export interface FuturesAccountInformation {
84 | feeTier: number;
85 | canTrade: boolean;
86 | canDeposit: boolean;
87 | canWithdraw: boolean;
88 | updateTime: number;
89 | totalInitialMargin: string;
90 | totalMaintMargin: string;
91 | totalWalletBalance: string;
92 | totalUnrealizedProfit: string;
93 | totalMarginBalance: string;
94 | totalPositionInitialMargin: string;
95 | totalOpenOrderInitialMargin: string;
96 | totalCrossWalletBalance: string;
97 | totalCrossUnPnl: string;
98 | availableBalance: string;
99 | maxWithdrawAmount: string;
100 | assets: FuturesAccountBalance[];
101 | positions: FuturesPosition[];
102 | }
103 |
```
--------------------------------------------------------------------------------
/src/services/binance.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { Spot } from '@binance/connector';
2 | import { BinanceCredentials, SpotOrder, OrderResponse, AccountBalance, ExchangeInfo } from '../types/binance.js';
3 | import { BinanceClientError, OrderValidationError } from '../types/errors.js';
4 | import { getApiKeys } from './keystore.js';
5 | import * as fs from 'fs';
6 | import * as path from 'path';
7 |
8 | const logFile = path.join(process.cwd(), 'logs', 'binance.log');
9 |
10 | // 确保日志目录存在
11 | if (!fs.existsSync(path.dirname(logFile))) {
12 | fs.mkdirSync(path.dirname(logFile), { recursive: true });
13 | }
14 |
15 | function log(message: string) {
16 | const timestamp = new Date().toISOString();
17 | fs.appendFileSync(logFile, `${timestamp} - ${message}\n`);
18 | }
19 |
20 | function logError(message: string, error?: unknown) {
21 | const timestamp = new Date().toISOString();
22 | const errorMessage = error instanceof Error ? error.message : String(error);
23 | fs.appendFileSync(logFile, `${timestamp} - ERROR: ${message} ${error ? `- ${errorMessage}` : ''}\n`);
24 | }
25 |
26 | let client: Spot | null = null;
27 |
28 | export async function initializeBinanceClient(): Promise<boolean> {
29 | log('Initializing Binance spot client...');
30 | const credentials = await getApiKeys();
31 | if (!credentials) {
32 | log('No credentials available for Binance spot client');
33 | return false;
34 | }
35 |
36 | try {
37 | log('Creating Binance spot client...');
38 | client = new Spot(credentials.apiKey, credentials.apiSecret);
39 |
40 | // Test the connection
41 | log('Testing Binance spot client connection...');
42 | await client.account();
43 | log('Successfully connected to Binance spot API');
44 | return true;
45 | } catch (error) {
46 | logError('Failed to initialize Binance spot client:', error);
47 | client = null;
48 | return false;
49 | }
50 | }
51 |
52 | export async function createSpotOrder(order: SpotOrder): Promise<OrderResponse> {
53 | if (!client) {
54 | throw new BinanceClientError('Binance client not initialized');
55 | }
56 |
57 | try {
58 | log(`Creating spot order: ${JSON.stringify(order)}`);
59 | const params: any = {
60 | symbol: order.symbol,
61 | side: order.side,
62 | type: order.type,
63 | };
64 |
65 | // 处理标准订单参数
66 | if (order.quantity) params.quantity = order.quantity;
67 | if (order.price) params.price = order.price;
68 | if (order.timeInForce) params.timeInForce = order.timeInForce;
69 |
70 | // 处理报价资产数量订单参数
71 | if (order.quoteOrderQty) {
72 | params.quoteOrderQty = order.quoteOrderQty;
73 | log(`Using quoteOrderQty: ${order.quoteOrderQty}`);
74 | }
75 |
76 | if (order.type === 'LIMIT' && !order.price) {
77 | throw new OrderValidationError('Price is required for LIMIT orders');
78 | }
79 |
80 | // 对于市价单,必须提供quantity或quoteOrderQty其中之一
81 | if (order.type === 'MARKET' && !order.quantity && !order.quoteOrderQty) {
82 | throw new OrderValidationError('For MARKET orders, either quantity or quoteOrderQty must be provided');
83 | }
84 |
85 | log(`Sending order with params: ${JSON.stringify(params)}`);
86 | const response = await client.newOrder(params);
87 | log(`Order created successfully: ${JSON.stringify(response.data)}`);
88 | return response.data;
89 | } catch (error) {
90 | if (error instanceof OrderValidationError) {
91 | throw error;
92 | }
93 | if (error instanceof Error) {
94 | logError(`Failed to create spot order:`, error);
95 | throw new BinanceClientError(`Failed to create spot order: ${error.message}`);
96 | }
97 | logError('Failed to create spot order: Unknown error', error);
98 | throw new BinanceClientError('Failed to create spot order: Unknown error');
99 | }
100 | }
101 |
102 | export async function cancelOrder(symbol: string, orderId: number): Promise<void> {
103 | if (!client) {
104 | throw new BinanceClientError('Binance client not initialized');
105 | }
106 |
107 | try {
108 | await client.cancelOrder(symbol, { orderId });
109 | } catch (error) {
110 | if (error instanceof Error) {
111 | throw new BinanceClientError(`Failed to cancel order: ${error.message}`);
112 | }
113 | throw new BinanceClientError('Failed to cancel order: Unknown error');
114 | }
115 | }
116 |
117 | export async function getAccountBalances(): Promise<AccountBalance[]> {
118 | if (!client) {
119 | throw new BinanceClientError('Binance client not initialized');
120 | }
121 |
122 | try {
123 | const response = await client.account();
124 | return response.data.balances;
125 | } catch (error) {
126 | if (error instanceof Error) {
127 | throw new BinanceClientError(`Failed to get account balances: ${error.message}`);
128 | }
129 | throw new BinanceClientError('Failed to get account balances: Unknown error');
130 | }
131 | }
132 |
133 | export async function getOpenOrders(symbol?: string): Promise<OrderResponse[]> {
134 | if (!client) {
135 | throw new BinanceClientError('Binance client not initialized');
136 | }
137 |
138 | try {
139 | const params = symbol ? { symbol } : {};
140 | const response = await client.openOrders(params);
141 | return response.data.map(order => ({
142 | ...order,
143 | transactTime: Date.now()
144 | }));
145 | } catch (error) {
146 | if (error instanceof Error) {
147 | throw new BinanceClientError(`Failed to get open orders: ${error.message}`);
148 | }
149 | throw new BinanceClientError('Failed to get open orders: Unknown error');
150 | }
151 | }
152 |
153 | // 仅保留可以确认正常工作的API
154 | // 其他功能暂时移除
155 |
```
--------------------------------------------------------------------------------
/src/services/tools.ts:
--------------------------------------------------------------------------------
```typescript
1 | export const configureApiKeysTool = {
2 | name: "configure_api_keys",
3 | description: "Configure Binance API keys for trading",
4 | inputSchema: {
5 | type: "object",
6 | properties: {
7 | apiKey: {
8 | type: "string",
9 | description: "Binance API key"
10 | },
11 | apiSecret: {
12 | type: "string",
13 | description: "Binance API secret"
14 | }
15 | },
16 | required: ["apiKey", "apiSecret"],
17 | },
18 | };
19 |
20 | export const createOrderTool = {
21 | name: "create_spot_order",
22 | description: "Create a new spot order on Binance",
23 | inputSchema: {
24 | type: "object",
25 | properties: {
26 | symbol: {
27 | type: "string",
28 | description: "Trading pair symbol (e.g., BTCUSDT)"
29 | },
30 | side: {
31 | type: "string",
32 | enum: ["BUY", "SELL"],
33 | description: "Order side"
34 | },
35 | type: {
36 | type: "string",
37 | enum: ["LIMIT", "MARKET"],
38 | description: "Order type"
39 | },
40 | quantity: {
41 | type: "string",
42 | description: "Order quantity (amount of base asset)"
43 | },
44 | quoteOrderQty: {
45 | type: "string",
46 | description: "Quote order quantity (amount of quote asset to spend or receive, e.g. USDT)"
47 | },
48 | price: {
49 | type: "string",
50 | description: "Order price (required for LIMIT orders)"
51 | },
52 | timeInForce: {
53 | type: "string",
54 | enum: ["GTC", "IOC", "FOK"],
55 | description: "Time in force"
56 | }
57 | },
58 | required: ["symbol", "side", "type"],
59 | },
60 | };
61 |
62 | export const cancelOrderTool = {
63 | name: "cancel_order",
64 | description: "Cancel an existing order",
65 | inputSchema: {
66 | type: "object",
67 | properties: {
68 | symbol: {
69 | type: "string",
70 | description: "Trading pair symbol (e.g., BTCUSDT)"
71 | },
72 | orderId: {
73 | type: "number",
74 | description: "Order ID to cancel"
75 | }
76 | },
77 | required: ["symbol", "orderId"],
78 | },
79 | };
80 |
81 | export const getBalancesTool = {
82 | name: "get_balances",
83 | description: "Get account balances",
84 | inputSchema: {
85 | type: "object",
86 | properties: {
87 | random_string: {
88 | type: "string",
89 | description: "Dummy parameter for no-parameter tools"
90 | }
91 | },
92 | required: ["random_string"],
93 | },
94 | };
95 |
96 | export const getOpenOrdersTool = {
97 | name: "get_open_orders",
98 | description: "Get open orders",
99 | inputSchema: {
100 | type: "object",
101 | properties: {
102 | symbol: {
103 | type: "string",
104 | description: "Trading pair symbol (optional)"
105 | }
106 | },
107 | required: [],
108 | },
109 | };
110 |
111 | export const createFuturesOrderTool = {
112 | name: "create_futures_order",
113 | description: "Create a new futures order on Binance",
114 | inputSchema: {
115 | type: "object",
116 | properties: {
117 | symbol: {
118 | type: "string",
119 | description: "Trading pair symbol (e.g., BTCUSDT)"
120 | },
121 | side: {
122 | type: "string",
123 | enum: ["BUY", "SELL"],
124 | description: "Order side"
125 | },
126 | positionSide: {
127 | type: "string",
128 | enum: ["BOTH", "LONG", "SHORT"],
129 | description: "Position side"
130 | },
131 | type: {
132 | type: "string",
133 | enum: ["LIMIT", "MARKET", "STOP", "STOP_MARKET", "TAKE_PROFIT", "TAKE_PROFIT_MARKET", "TRAILING_STOP_MARKET"],
134 | description: "Order type"
135 | },
136 | quantity: {
137 | type: "string",
138 | description: "Order quantity"
139 | },
140 | price: {
141 | type: "string",
142 | description: "Order price (required for LIMIT orders)"
143 | },
144 | stopPrice: {
145 | type: "string",
146 | description: "Stop price (required for STOP/TAKE_PROFIT orders)"
147 | },
148 | timeInForce: {
149 | type: "string",
150 | enum: ["GTC", "IOC", "FOK", "GTX"],
151 | description: "Time in force"
152 | },
153 | reduceOnly: {
154 | type: "boolean",
155 | description: "Reduce only flag"
156 | },
157 | closePosition: {
158 | type: "boolean",
159 | description: "Close position flag"
160 | }
161 | },
162 | required: ["symbol", "side", "type", "quantity"],
163 | },
164 | };
165 |
166 | export const cancelFuturesOrderTool = {
167 | name: "cancel_futures_order",
168 | description: "Cancel an existing futures order",
169 | inputSchema: {
170 | type: "object",
171 | properties: {
172 | symbol: {
173 | type: "string",
174 | description: "Trading pair symbol (e.g., BTCUSDT)"
175 | },
176 | orderId: {
177 | type: "number",
178 | description: "Order ID to cancel"
179 | }
180 | },
181 | required: ["symbol", "orderId"],
182 | },
183 | };
184 |
185 | export const getFuturesPositionsTool = {
186 | name: "get_futures_positions",
187 | description: "Get all futures positions",
188 | inputSchema: {
189 | type: "object",
190 | properties: {},
191 | required: [],
192 | },
193 | };
194 |
195 | export const setFuturesLeverageTool = {
196 | name: "set_futures_leverage",
197 | description: "Set leverage for a futures symbol",
198 | inputSchema: {
199 | type: "object",
200 | properties: {
201 | symbol: {
202 | type: "string",
203 | description: "Trading pair symbol (e.g., BTCUSDT)"
204 | },
205 | leverage: {
206 | type: "number",
207 | description: "Leverage value (1-125)"
208 | }
209 | },
210 | required: ["symbol", "leverage"],
211 | },
212 | };
213 |
214 | export const getFuturesAccountTool = {
215 | name: "get_futures_account",
216 | description: "Get futures account information",
217 | inputSchema: {
218 | type: "object",
219 | properties: {},
220 | required: [],
221 | },
222 | };
223 |
224 | export const getFuturesOpenOrdersTool = {
225 | name: "get_futures_open_orders",
226 | description: "Get open futures orders",
227 | inputSchema: {
228 | type: "object",
229 | properties: {
230 | symbol: {
231 | type: "string",
232 | description: "Trading pair symbol (optional)"
233 | }
234 | },
235 | required: [],
236 | },
237 | };
238 |
239 | export const getFundingRateTool = {
240 | name: "get_funding_rate",
241 | description: "Get funding rate for a futures symbol",
242 | inputSchema: {
243 | type: "object",
244 | properties: {
245 | symbol: {
246 | type: "string",
247 | description: "Trading pair symbol (e.g., BTCUSDT)"
248 | }
249 | },
250 | required: ["symbol"],
251 | },
252 | };
253 |
```
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
```typescript
1 | #!/usr/bin/env node
2 |
3 | import {
4 | CallToolRequestSchema,
5 | ListToolsRequestSchema,
6 | } from "@modelcontextprotocol/sdk/types.js";
7 |
8 | import { SpotOrder } from "./types/binance.js";
9 | import {
10 | storeApiKeys,
11 | getApiKeys,
12 | deleteApiKeys,
13 | } from "./services/keystore.js";
14 | import {
15 | createSpotOrder,
16 | cancelOrder,
17 | getAccountBalances,
18 | getOpenOrders,
19 | } from "./services/binance.js";
20 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
21 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
22 | import dotenv from "dotenv";
23 | import { initializeBinanceClient } from "./services/binance.js";
24 | import {
25 | configureApiKeysTool,
26 | createOrderTool,
27 | cancelOrderTool,
28 | getBalancesTool,
29 | getOpenOrdersTool,
30 | createFuturesOrderTool,
31 | cancelFuturesOrderTool,
32 | getFuturesPositionsTool,
33 | setFuturesLeverageTool,
34 | getFuturesAccountTool,
35 | getFuturesOpenOrdersTool,
36 | getFundingRateTool,
37 | } from "./services/tools.js";
38 | import {
39 | createFuturesOrder,
40 | cancelFuturesOrder,
41 | getFuturesPositions,
42 | setFuturesLeverage,
43 | getFuturesAccountInformation as getFuturesAccount,
44 | getFuturesOpenOrders,
45 | getFundingRate,
46 | initializeFuturesClient,
47 | changePositionMode,
48 | changeMarginType,
49 | getFuturesKlines,
50 | } from "./services/binanceFutures.js";
51 | import { FuturesOrder, LeverageSettings, TimeInForce, PositionSide, WorkingType, MarginType } from "./types/futures.js";
52 | import * as fs from 'fs';
53 | import * as path from 'path';
54 | import { z } from "zod";
55 |
56 | // Load environment variables first
57 | dotenv.config();
58 |
59 | const logFile = path.join(process.cwd(), 'logs', 'server.log');
60 |
61 | // 确保日志目录存在
62 | if (!fs.existsSync(path.dirname(logFile))) {
63 | fs.mkdirSync(path.dirname(logFile), { recursive: true });
64 | }
65 |
66 | function log(message: string) {
67 | const timestamp = new Date().toISOString();
68 | fs.appendFileSync(logFile, `${timestamp} - ${message}\n`);
69 | }
70 |
71 | function logError(message: string, error?: unknown) {
72 | const timestamp = new Date().toISOString();
73 | const errorMessage = error instanceof Error ? error.message : String(error);
74 | fs.appendFileSync(logFile, `${timestamp} - ERROR: ${message} ${error ? `- ${errorMessage}` : ''}\n`);
75 | }
76 |
77 | // 创建高级McpServer实例,而不是低级Server实例
78 | const server = new McpServer({
79 | name: "mcp-server-binance",
80 | version: "0.1.0",
81 | });
82 |
83 | // 注册工具
84 | server.tool(
85 | "configure_api_keys",
86 | {
87 | apiKey: z.string().describe("Binance API key"),
88 | apiSecret: z.string().describe("Binance API secret")
89 | },
90 | async ({ apiKey, apiSecret }) => {
91 | await storeApiKeys(apiKey, apiSecret);
92 | const spotInitialized = await initializeBinanceClient();
93 | const futuresInitialized = await initializeFuturesClient();
94 | return {
95 | content: [
96 | {
97 | type: "text",
98 | text: spotInitialized && futuresInitialized
99 | ? "API keys configured successfully"
100 | : "Failed to initialize Binance clients",
101 | },
102 | ],
103 | };
104 | }
105 | );
106 |
107 | server.tool(
108 | "create_spot_order",
109 | {
110 | symbol: z.string().describe("Trading pair symbol (e.g., BTCUSDT)"),
111 | side: z.enum(["BUY", "SELL"]).describe("Order side"),
112 | type: z.enum(["LIMIT", "MARKET"]).describe("Order type"),
113 | quantity: z.string().optional().describe("Order quantity (amount of base asset)"),
114 | quoteOrderQty: z.string().optional().describe("Quote order quantity (amount of quote asset to spend or receive, e.g. USDT)"),
115 | price: z.string().optional().describe("Order price (required for LIMIT orders)"),
116 | timeInForce: z.enum(["GTC", "IOC", "FOK"]).optional().describe("Time in force")
117 | },
118 | async (args) => {
119 | const order: SpotOrder = {
120 | symbol: args.symbol,
121 | side: args.side,
122 | type: args.type,
123 | quantity: args.quantity,
124 | quoteOrderQty: args.quoteOrderQty,
125 | price: args.price,
126 | timeInForce: args.timeInForce,
127 | };
128 | const response = await createSpotOrder(order);
129 | return {
130 | content: [
131 | {
132 | type: "text",
133 | text: JSON.stringify(response),
134 | },
135 | ],
136 | };
137 | }
138 | );
139 |
140 | server.tool(
141 | "cancel_order",
142 | {
143 | symbol: z.string().describe("Trading pair symbol (e.g., BTCUSDT)"),
144 | orderId: z.number().describe("Order ID to cancel")
145 | },
146 | async ({ symbol, orderId }) => {
147 | await cancelOrder(symbol, orderId);
148 | return {
149 | content: [
150 | {
151 | type: "text",
152 | text: "Order cancelled successfully",
153 | },
154 | ],
155 | };
156 | }
157 | );
158 |
159 | server.tool(
160 | "get_balances",
161 | {},
162 | async () => {
163 | const balances = await getAccountBalances();
164 | return {
165 | content: [
166 | {
167 | type: "text",
168 | text: JSON.stringify(balances),
169 | },
170 | ],
171 | };
172 | }
173 | );
174 |
175 | server.tool(
176 | "get_open_orders",
177 | {
178 | symbol: z.string().optional().describe("Trading pair symbol (optional)")
179 | },
180 | async ({ symbol }) => {
181 | const orders = await getOpenOrders(symbol);
182 | return {
183 | content: [
184 | {
185 | type: "text",
186 | text: JSON.stringify(orders),
187 | },
188 | ],
189 | };
190 | }
191 | );
192 |
193 | server.tool(
194 | "create_futures_order",
195 | {
196 | symbol: z.string().describe("Trading pair symbol (e.g., BTCUSDT)"),
197 | side: z.enum(["BUY", "SELL"]).describe("Order side"),
198 | type: z.enum([
199 | "LIMIT", "MARKET", "STOP", "STOP_MARKET",
200 | "TAKE_PROFIT", "TAKE_PROFIT_MARKET", "TRAILING_STOP_MARKET"
201 | ]).describe("Order type"),
202 | quantity: z.string().describe("Order quantity"),
203 | price: z.string().optional().describe("Order price (required for LIMIT orders)"),
204 | stopPrice: z.string().optional().describe("Stop price (required for STOP orders)"),
205 | timeInForce: z.enum(["GTC", "IOC", "FOK", "GTX"]).optional().describe("Time in force"),
206 | reduceOnly: z.boolean().optional().describe("Reduce only flag"),
207 | closePosition: z.boolean().optional().describe("Close position flag"),
208 | positionSide: z.enum(["BOTH", "LONG", "SHORT"]).optional().describe("Position side"),
209 | workingType: z.enum(["MARK_PRICE", "CONTRACT_PRICE"]).optional().describe("Working type"),
210 | priceProtect: z.boolean().optional().describe("Price protect flag"),
211 | activationPrice: z.string().optional().describe("Activation price for TRAILING_STOP_MARKET orders"),
212 | callbackRate: z.string().optional().describe("Callback rate for TRAILING_STOP_MARKET orders")
213 | },
214 | async (args) => {
215 | const order: FuturesOrder = {
216 | symbol: args.symbol,
217 | side: args.side,
218 | type: args.type,
219 | quantity: args.quantity,
220 | price: args.price,
221 | stopPrice: args.stopPrice,
222 | timeInForce: args.timeInForce as TimeInForce,
223 | reduceOnly: args.reduceOnly,
224 | closePosition: args.closePosition,
225 | positionSide: args.positionSide as PositionSide,
226 | workingType: args.workingType as WorkingType,
227 | priceProtect: args.priceProtect,
228 | activationPrice: args.activationPrice,
229 | callbackRate: args.callbackRate,
230 | };
231 | const response = await createFuturesOrder(order);
232 | return {
233 | content: [
234 | {
235 | type: "text",
236 | text: JSON.stringify(response),
237 | },
238 | ],
239 | };
240 | }
241 | );
242 |
243 | server.tool(
244 | "cancel_futures_order",
245 | {
246 | symbol: z.string().describe("Trading pair symbol (e.g., BTCUSDT)"),
247 | orderId: z.number().describe("Order ID to cancel")
248 | },
249 | async ({ symbol, orderId }) => {
250 | await cancelFuturesOrder(symbol, orderId);
251 | return {
252 | content: [
253 | {
254 | type: "text",
255 | text: "Futures order cancelled successfully",
256 | },
257 | ],
258 | };
259 | }
260 | );
261 |
262 | server.tool(
263 | "get_futures_positions",
264 | {},
265 | async () => {
266 | const positions = await getFuturesPositions();
267 | return {
268 | content: [
269 | {
270 | type: "text",
271 | text: JSON.stringify(positions),
272 | },
273 | ],
274 | };
275 | }
276 | );
277 |
278 | server.tool(
279 | "set_futures_leverage",
280 | {
281 | symbol: z.string().describe("Trading pair symbol (e.g., BTCUSDT)"),
282 | leverage: z.number().describe("Leverage value (1-125)")
283 | },
284 | async ({ symbol, leverage }) => {
285 | const settings: LeverageSettings = {
286 | symbol: symbol,
287 | leverage: leverage,
288 | };
289 | await setFuturesLeverage(settings);
290 | return {
291 | content: [
292 | {
293 | type: "text",
294 | text: "Leverage set successfully",
295 | },
296 | ],
297 | };
298 | }
299 | );
300 |
301 | server.tool(
302 | "get_futures_account",
303 | {},
304 | async () => {
305 | const account = await getFuturesAccount();
306 | return {
307 | content: [
308 | {
309 | type: "text",
310 | text: JSON.stringify(account),
311 | },
312 | ],
313 | };
314 | }
315 | );
316 |
317 | server.tool(
318 | "get_futures_open_orders",
319 | {
320 | symbol: z.string().optional().describe("Trading pair symbol (optional)")
321 | },
322 | async ({ symbol }) => {
323 | const orders = await getFuturesOpenOrders(symbol);
324 | return {
325 | content: [
326 | {
327 | type: "text",
328 | text: JSON.stringify(orders),
329 | },
330 | ],
331 | };
332 | }
333 | );
334 |
335 | server.tool(
336 | "get_funding_rate",
337 | {
338 | symbol: z.string().describe("Trading pair symbol (e.g., BTCUSDT)")
339 | },
340 | async ({ symbol }) => {
341 | const rate = await getFundingRate(symbol);
342 | return {
343 | content: [
344 | {
345 | type: "text",
346 | text: JSON.stringify(rate),
347 | },
348 | ],
349 | };
350 | }
351 | );
352 |
353 | async function main() {
354 | try {
355 | // Initialize Binance clients
356 | const spotInitialized = await initializeBinanceClient();
357 | const futuresInitialized = await initializeFuturesClient();
358 |
359 | if (!spotInitialized || !futuresInitialized) {
360 | logError('Binance clients not initialized');
361 | } else {
362 | log('Binance clients initialized successfully');
363 | }
364 |
365 | // 使用stdio传输层
366 | const transport = new StdioServerTransport();
367 | await server.connect(transport);
368 | log('Server started successfully with stdio transport');
369 | } catch (error) {
370 | logError('Failed to start server:', error);
371 | process.exit(1);
372 | }
373 | }
374 |
375 | main().catch((error) => {
376 | logError('Unhandled error:', error);
377 | process.exit(1);
378 | });
379 |
```
--------------------------------------------------------------------------------
/src/services/binanceFutures.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { Spot } from '@binance/connector';
2 | import { BinanceClientError, ApiKeyError, OrderValidationError, InsufficientMarginError, InvalidPositionModeError } from '../types/errors.js';
3 | import { getApiKeys } from './keystore.js';
4 | import * as fs from 'fs';
5 | import * as path from 'path';
6 | import {
7 | FuturesOrder,
8 | FuturesPosition,
9 | FuturesAccountInformation,
10 | LeverageSettings,
11 | FundingRate,
12 | PositionSide,
13 | MarginType,
14 | WorkingType
15 | } from '../types/futures.js';
16 |
17 | const logFile = path.join(process.cwd(), 'logs', 'futures.log');
18 |
19 | // 确保日志目录存在
20 | if (!fs.existsSync(path.dirname(logFile))) {
21 | fs.mkdirSync(path.dirname(logFile), { recursive: true });
22 | }
23 |
24 | function log(message: string) {
25 | const timestamp = new Date().toISOString();
26 | fs.appendFileSync(logFile, `${timestamp} - ${message}\n`);
27 | }
28 |
29 | function logError(message: string, error?: unknown) {
30 | const timestamp = new Date().toISOString();
31 | const errorMessage = error instanceof Error ? error.message : String(error);
32 | fs.appendFileSync(logFile, `${timestamp} - ERROR: ${message} ${error ? `- ${errorMessage}` : ''}\n`);
33 | }
34 |
35 | interface SpotExtended extends Spot {
36 | changeLeverage: (params: { symbol: string; leverage: number }) => Promise<any>;
37 | premiumIndex: (params: { symbol: string }) => Promise<any>;
38 | changePositionMode: (params: { dualSidePosition: boolean }) => Promise<any>;
39 | changeMarginType: (params: { symbol: string; marginType: MarginType }) => Promise<any>;
40 | klines: (symbol: string, interval: string, options?: any) => Promise<any>;
41 | }
42 |
43 | let futuresClient: SpotExtended | null = null;
44 |
45 | export async function initializeFuturesClient(): Promise<boolean> {
46 | log('Initializing Binance futures client...');
47 | try {
48 | const keys = await getApiKeys();
49 | if (!keys) {
50 | logError('No credentials available for Binance futures client');
51 | throw new ApiKeyError('Futures API keys not found');
52 | }
53 | const { apiKey, apiSecret } = keys;
54 |
55 | log('Creating Binance futures client...');
56 | // Initialize client for USDⓈ-M Futures
57 | futuresClient = new Spot(apiKey, apiSecret) as SpotExtended;
58 |
59 | // Test the connection
60 | log('Testing Binance futures client connection...');
61 | await futuresClient.account();
62 | log('Successfully connected to Binance futures API');
63 | return true;
64 | } catch (error) {
65 | logError('Failed to initialize futures client:', error);
66 | futuresClient = null;
67 | if (error instanceof ApiKeyError) {
68 | throw error;
69 | }
70 | throw new BinanceClientError(`Failed to initialize futures client: ${error instanceof Error ? error.message : 'Unknown error'}`);
71 | }
72 | }
73 |
74 | export async function createFuturesOrder(order: FuturesOrder): Promise<any> {
75 | if (!futuresClient) {
76 | logError('Attempted to create futures order without initialized client');
77 | throw new BinanceClientError('Futures client not initialized');
78 | }
79 |
80 | try {
81 | log(`Creating futures order: ${JSON.stringify(order, null, 2)}`);
82 | const params = {
83 | symbol: order.symbol,
84 | side: order.side,
85 | type: order.type,
86 | quantity: order.quantity,
87 | timeInForce: order.timeInForce || 'GTC'
88 | } as any;
89 |
90 | if (order.positionSide) params.positionSide = order.positionSide;
91 | if (order.price) params.price = order.price;
92 | if (order.stopPrice) params.stopPrice = order.stopPrice;
93 | if (order.reduceOnly !== undefined) params.reduceOnly = order.reduceOnly;
94 | if (order.workingType) params.workingType = order.workingType;
95 | if (order.activationPrice) params.activationPrice = order.activationPrice;
96 | if (order.callbackRate) params.callbackRate = order.callbackRate;
97 | if (order.closePosition !== undefined) params.closePosition = order.closePosition;
98 | if (order.priceProtect !== undefined) params.priceProtect = order.priceProtect;
99 |
100 | // Validate required parameters based on order type
101 | if (['LIMIT', 'STOP', 'TAKE_PROFIT'].includes(order.type) && !order.price) {
102 | throw new OrderValidationError(`Price is required for ${order.type} orders`);
103 | }
104 | if (['STOP', 'STOP_MARKET', 'TAKE_PROFIT', 'TAKE_PROFIT_MARKET'].includes(order.type) && !order.stopPrice) {
105 | throw new OrderValidationError(`Stop price is required for ${order.type} orders`);
106 | }
107 | if (order.type === 'TRAILING_STOP_MARKET' && !order.callbackRate) {
108 | throw new OrderValidationError('Callback rate is required for TRAILING_STOP_MARKET orders');
109 | }
110 |
111 | log(`Sending futures order request with params: ${JSON.stringify(params, null, 2)}`);
112 | const response = await futuresClient.newOrder(params);
113 | log(`Futures order created successfully: ${JSON.stringify(response, null, 2)}`);
114 | return response.data;
115 | } catch (error) {
116 | logError('Error creating futures order:', error);
117 | if (error instanceof OrderValidationError) {
118 | throw error;
119 | }
120 | const errorMessage = error instanceof Error ? error.message : 'Unknown error';
121 | if (errorMessage.includes('insufficient margin')) {
122 | logError('Insufficient margin for futures order');
123 | throw new InsufficientMarginError(`Insufficient margin to create futures order: ${errorMessage}`);
124 | }
125 | if (errorMessage.includes('invalid position mode')) {
126 | logError('Invalid position mode for futures order');
127 | throw new InvalidPositionModeError(`Invalid position mode for futures order: ${errorMessage}`);
128 | }
129 | throw new BinanceClientError(`Failed to create futures order: ${errorMessage}`);
130 | }
131 | }
132 |
133 | export async function getFuturesAccountInformation(): Promise<FuturesAccountInformation> {
134 | if (!futuresClient) {
135 | logError('Attempted to get futures account information without initialized client');
136 | throw new BinanceClientError('Futures client not initialized');
137 | }
138 |
139 | try {
140 | log('Fetching futures account information...');
141 | const response = await futuresClient.account();
142 | log('Successfully retrieved futures account information');
143 | const accountInfo = {
144 | feeTier: 0,
145 | canTrade: true,
146 | canDeposit: true,
147 | canWithdraw: true,
148 | updateTime: Date.now(),
149 | totalInitialMargin: '0',
150 | totalMaintMargin: '0',
151 | totalWalletBalance: '0',
152 | totalUnrealizedProfit: '0',
153 | totalMarginBalance: '0',
154 | totalPositionInitialMargin: '0',
155 | totalOpenOrderInitialMargin: '0',
156 | totalCrossWalletBalance: '0',
157 | totalCrossUnPnl: '0',
158 | availableBalance: '0',
159 | maxWithdrawAmount: '0',
160 | assets: response.data.balances.map((balance: any) => ({
161 | asset: balance.asset,
162 | walletBalance: balance.free,
163 | unrealizedProfit: '0',
164 | marginBalance: balance.locked,
165 | maintMargin: '0',
166 | initialMargin: '0',
167 | positionInitialMargin: '0',
168 | openOrderInitialMargin: '0',
169 | maxWithdrawAmount: balance.free,
170 | crossWalletBalance: '0',
171 | crossUnPnl: '0',
172 | availableBalance: balance.free
173 | })),
174 | positions: []
175 | };
176 | return accountInfo;
177 | } catch (error) {
178 | logError('Error getting futures account information:', error);
179 | throw new BinanceClientError(`Failed to get futures account information: ${error instanceof Error ? error.message : 'Unknown error'}`);
180 | }
181 | }
182 |
183 | export async function getFuturesPositions(): Promise<FuturesPosition[]> {
184 | if (!futuresClient) {
185 | throw new BinanceClientError('Futures client not initialized');
186 | }
187 |
188 | try {
189 | const response = await futuresClient.account();
190 | const accountData = response.data as any;
191 | const positions = accountData.positions || [];
192 | return positions.map((pos: any) => ({
193 | symbol: pos.symbol || '',
194 | positionAmt: pos.positionAmt || '0',
195 | entryPrice: pos.entryPrice || '0',
196 | markPrice: pos.markPrice || '0',
197 | unRealizedProfit: pos.unrealizedProfit || '0',
198 | liquidationPrice: pos.liquidationPrice || '0',
199 | leverage: parseInt(pos.leverage || '1'),
200 | marginType: pos.marginType || MarginType.CROSSED,
201 | isolatedMargin: pos.isolatedMargin || '0',
202 | positionSide: pos.positionSide || PositionSide.BOTH,
203 | updateTime: pos.updateTime || Date.now()
204 | }));
205 | } catch (error) {
206 | throw new BinanceClientError(`Failed to get futures positions: ${error instanceof Error ? error.message : 'Unknown error'}`);
207 | }
208 | }
209 |
210 | export async function setFuturesLeverage(settings: LeverageSettings): Promise<boolean> {
211 | if (!futuresClient) {
212 | throw new BinanceClientError('Futures client not initialized');
213 | }
214 |
215 | try {
216 | await futuresClient.changeLeverage({
217 | symbol: settings.symbol,
218 | leverage: settings.leverage
219 | });
220 | return true;
221 | } catch (error) {
222 | const errorMessage = error instanceof Error ? error.message : 'Unknown error';
223 | if (errorMessage.includes('insufficient margin')) {
224 | throw new InsufficientMarginError(`Insufficient margin to change leverage: ${errorMessage}`);
225 | }
226 | if (errorMessage.includes('invalid position mode')) {
227 | throw new InvalidPositionModeError(`Invalid position mode when changing leverage: ${errorMessage}`);
228 | }
229 | throw new BinanceClientError(`Failed to set futures leverage: ${errorMessage}`);
230 | }
231 | }
232 |
233 | export async function getFundingRate(symbol: string): Promise<FundingRate> {
234 | if (!futuresClient) {
235 | throw new BinanceClientError('Futures client not initialized');
236 | }
237 |
238 | try {
239 | const response = await futuresClient.premiumIndex({ symbol });
240 | const data = response.data[0] || {};
241 | return {
242 | symbol: data.symbol || symbol,
243 | fundingRate: data.lastFundingRate || '0',
244 | fundingTime: data.nextFundingTime || Date.now(),
245 | nextFundingTime: data.nextFundingTime || Date.now()
246 | };
247 | } catch (error) {
248 | throw new BinanceClientError(`Failed to get funding rate: ${error instanceof Error ? error.message : 'Unknown error'}`);
249 | }
250 | }
251 |
252 | export async function cancelFuturesOrder(symbol: string, orderId: number): Promise<void> {
253 | if (!futuresClient) {
254 | throw new BinanceClientError('Futures client not initialized');
255 | }
256 |
257 | try {
258 | await futuresClient.cancelOrder(symbol, { orderId });
259 | } catch (error) {
260 | const errorMessage = error instanceof Error ? error.message : 'Unknown error';
261 | if (errorMessage.includes('invalid position mode')) {
262 | throw new InvalidPositionModeError(`Invalid position mode when canceling futures order: ${errorMessage}`);
263 | }
264 | throw new BinanceClientError(`Failed to cancel futures order: ${errorMessage}`);
265 | }
266 | }
267 |
268 | export async function getFuturesOpenOrders(symbol?: string): Promise<FuturesOrder[]> {
269 | if (!futuresClient) {
270 | throw new BinanceClientError('Futures client not initialized');
271 | }
272 |
273 | try {
274 | const params = symbol ? { symbol } : {};
275 | const response = await futuresClient.openOrders(params);
276 | return response.data.map((order: any) => ({
277 | symbol: order.symbol,
278 | side: order.side,
279 | type: order.type,
280 | quantity: order.origQty,
281 | price: order.price,
282 | timeInForce: order.timeInForce,
283 | positionSide: order.positionSide,
284 | stopPrice: order.stopPrice,
285 | workingType: order.workingType,
286 | closePosition: order.closePosition,
287 | activationPrice: order.activationPrice,
288 | callbackRate: order.callbackRate,
289 | priceProtect: order.priceProtect,
290 | reduceOnly: order.reduceOnly
291 | }));
292 | } catch (error) {
293 | throw new BinanceClientError(`Failed to get open futures orders: ${error instanceof Error ? error.message : 'Unknown error'}`);
294 | }
295 | }
296 |
297 | export async function changePositionMode(dualSidePosition: boolean): Promise<boolean> {
298 | if (!futuresClient) {
299 | throw new BinanceClientError('Futures client not initialized');
300 | }
301 |
302 | try {
303 | log(`Changing position mode to ${dualSidePosition ? 'dual-side' : 'one-way'}`);
304 | await futuresClient.changePositionMode({
305 | dualSidePosition: dualSidePosition
306 | });
307 | log('Position mode changed successfully');
308 | return true;
309 | } catch (error) {
310 | const errorMessage = error instanceof Error ? error.message : 'Unknown error';
311 | logError(`Failed to change position mode:`, error);
312 | throw new BinanceClientError(`Failed to change position mode: ${errorMessage}`);
313 | }
314 | }
315 |
316 | export async function changeMarginType(symbol: string, marginType: MarginType): Promise<boolean> {
317 | if (!futuresClient) {
318 | throw new BinanceClientError('Futures client not initialized');
319 | }
320 |
321 | try {
322 | log(`Changing margin type for ${symbol} to ${marginType}`);
323 | await futuresClient.changeMarginType({
324 | symbol: symbol,
325 | marginType: marginType
326 | });
327 | log('Margin type changed successfully');
328 | return true;
329 | } catch (error) {
330 | const errorMessage = error instanceof Error ? error.message : 'Unknown error';
331 | logError(`Failed to change margin type:`, error);
332 | if (errorMessage.includes('isolated balance insufficient')) {
333 | throw new InsufficientMarginError(`Insufficient margin to change margin type: ${errorMessage}`);
334 | }
335 | throw new BinanceClientError(`Failed to change margin type: ${errorMessage}`);
336 | }
337 | }
338 |
339 | export async function getFuturesKlines(symbol: string, interval: string, limit?: number): Promise<any[]> {
340 | if (!futuresClient) {
341 | throw new BinanceClientError('Futures client not initialized');
342 | }
343 |
344 | try {
345 | const params: any = {};
346 | if (limit) params.limit = limit;
347 |
348 | log(`Querying futures klines for ${symbol}, interval ${interval}`);
349 | const response = await futuresClient.klines(symbol, interval, params);
350 | log('Futures klines retrieved successfully');
351 |
352 | // 转换返回数据为更易于使用的格式
353 | return response.data.map((kline: any[]) => ({
354 | openTime: kline[0],
355 | open: kline[1],
356 | high: kline[2],
357 | low: kline[3],
358 | close: kline[4],
359 | volume: kline[5],
360 | closeTime: kline[6],
361 | quoteAssetVolume: kline[7],
362 | trades: kline[8],
363 | takerBuyBaseAssetVolume: kline[9],
364 | takerBuyQuoteAssetVolume: kline[10]
365 | }));
366 | } catch (error) {
367 | const errorMessage = error instanceof Error ? error.message : 'Unknown error';
368 | logError(`Failed to get futures klines:`, error);
369 | throw new BinanceClientError(`Failed to get futures klines: ${errorMessage}`);
370 | }
371 | }
372 |
```