#
tokens: 13341/50000 16/16 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .gitignore
├── jest.config.js
├── LICENSE
├── package-lock.json
├── package.json
├── README.md
├── setup.sh
├── src
│   ├── __tests__
│   │   └── binance-ws.test.ts
│   ├── config.ts
│   ├── connectors
│   │   ├── binance-rest.ts
│   │   └── binance-ws.ts
│   ├── index.ts
│   ├── types
│   │   ├── api-types.ts
│   │   ├── market-data.ts
│   │   └── ws-stream.ts
│   └── utils
│       └── logger.ts
├── tsconfig.json
└── tsconfig.test.json
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
1 | node_modules/
2 | build/
3 | *.log
4 | .env*
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
 1 | # Binance MCP Server
 2 | 
 3 | A Model Context Protocol (MCP) server implementation for Binance market data with WebSocket support.
 4 | 
 5 | ## Features
 6 | 
 7 | - Real-time market data streaming via WebSocket
 8 | - Support for both spot and futures markets
 9 | - Automatic reconnection with exponential backoff
10 | - Type-safe message handling
11 | - Comprehensive error handling
12 | 
13 | ## Installation
14 | 
15 | ```bash
16 | npm install
17 | ```
18 | 
19 | ## Usage
20 | 
21 | ### Starting the Server
22 | 
23 | ```bash
24 | npm start
25 | ```
26 | 
27 | ### WebSocket Stream Types
28 | 
29 | The following stream types are supported:
30 | 
31 | - `trade`: Real-time trade data
32 | - `ticker`: 24-hour rolling window price change statistics
33 | - `bookTicker`: Best bid/ask price and quantity
34 | - `kline`: Candlestick data
35 | - `markPrice`: Mark price and funding rate (futures only)
36 | - `fundingRate`: Funding rate data (futures only)
37 | 
38 | ### Example Usage in Claude Desktop
39 | 
40 | ```typescript
41 | // Subscribe to trade and ticker streams for BTC/USDT
42 | await server.subscribe('BTCUSDT', 'spot', ['trade', 'ticker']);
43 | 
44 | // Handle incoming data
45 | server.onStreamData('BTCUSDT', 'trade', (data) => {
46 |   console.log('New trade:', data);
47 | });
48 | ```
49 | 
50 | ## Development
51 | 
52 | ### Running Tests
53 | 
54 | ```bash
55 | npm test
56 | ```
57 | 
58 | ### Building
59 | 
60 | ```bash
61 | npm run build
62 | ```
63 | 
64 | ## License
65 | 
66 | Private
67 | 
```

--------------------------------------------------------------------------------
/tsconfig.test.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "extends": "./tsconfig.json",
 3 |   "compilerOptions": {
 4 |     "types": ["jest", "node"],
 5 |     "isolatedModules": false
 6 |   },
 7 |   "include": ["src/**/*.ts"],
 8 |   "exclude": ["node_modules"]
 9 | }
10 | 
```

--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------

```javascript
 1 | /** @type {import('ts-jest').JestConfigWithTsJest} */
 2 | module.exports = {
 3 |   preset: 'ts-jest',
 4 |   testEnvironment: 'node',
 5 |   moduleNameMapper: {
 6 |     '^@/(.*)$': '<rootDir>/src/$1'
 7 |   },
 8 |   testMatch: ['**/__tests__/**/*.test.ts'],
 9 |   transform: {
10 |     '^.+\\.tsx?$': ['ts-jest', {
11 |       tsconfig: 'tsconfig.test.json'
12 |     }]
13 |   },
14 |   moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node']
15 | };
```

--------------------------------------------------------------------------------
/src/utils/logger.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import winston from 'winston';
 2 | 
 3 | export const logger = winston.createLogger({
 4 |   level: 'info',
 5 |   format: winston.format.combine(
 6 |     winston.format.timestamp(),
 7 |     winston.format.json()
 8 |   ),
 9 |   transports: [
10 |     new winston.transports.File({ filename: 'error.log', level: 'error' }),
11 |     new winston.transports.File({ filename: 'combined.log' }),
12 |     new winston.transports.Console({
13 |       format: winston.format.combine(
14 |         winston.format.colorize(),
15 |         winston.format.simple()
16 |       )
17 |     })
18 |   ]
19 | });
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "ES2022",
 4 |     "module": "NodeNext",        // Changed from ES2022
 5 |     "moduleResolution": "NodeNext",
 6 |     "outDir": "./build",
 7 |     "rootDir": "./src",
 8 |     "strict": true,
 9 |     "esModuleInterop": true,
10 |     "skipLibCheck": true,
11 |     "forceConsistentCasingInFileNames": true,
12 |     "resolveJsonModule": true,
13 |     "declaration": true,
14 |     "baseUrl": ".",
15 |     "paths": {
16 |       "@/*": ["src/*"]
17 |     }
18 |   },
19 |   "include": ["src/**/*"],
20 |   "exclude": ["node_modules", "**/*.test.ts", "build"]
21 | }
```

--------------------------------------------------------------------------------
/src/types/market-data.ts:
--------------------------------------------------------------------------------

```typescript
 1 | export interface MarketData {
 2 |   symbol: string;
 3 |   exchange: string;
 4 |   type: 'spot' | 'futures';
 5 |   price: number;
 6 |   timestamp: number;
 7 |   volume24h: number;
 8 |   volumeDelta24h?: number;
 9 |   priceChange24h: number;
10 |   priceChange1h?: number;
11 |   price24hHigh: number;
12 |   price24hLow: number;
13 |   tradeCount24h: number;
14 |   bidAskSpread?: number;
15 |   openInterest?: number;
16 |   fundingRate?: number;
17 |   liquidations24h?: number;
18 | }
19 | 
20 | export interface OrderBookData {
21 |   symbol: string;
22 |   type: 'spot' | 'futures';
23 |   bids: [number, number][];
24 |   asks: [number, number][];
25 |   timestamp: number;
26 |   lastUpdateId: number;
27 | }
28 | 
29 | export interface TradeData {
30 |   symbol: string;
31 |   type: 'spot' | 'futures';
32 |   price: number;
33 |   quantity: number;
34 |   timestamp: number;
35 |   isBuyerMaker: boolean;
36 |   tradeId: number;
37 | }
```

--------------------------------------------------------------------------------
/src/config.ts:
--------------------------------------------------------------------------------

```typescript
 1 | export const config = {
 2 | 
 3 |   // Server config
 4 |   NAME: 'binance-market-data',
 5 |   VERSION: '1.0.0',
 6 |   
 7 |   // REST endpoints
 8 |   SPOT_REST_URL: 'https://api.binance.com/api/v3',
 9 |   FUTURES_REST_URL: 'https://fapi.binance.com/fapi/v1',
10 |   
11 |   // WebSocket endpoints
12 |   SPOT_WS_URL: 'wss://stream.binance.com:9443/ws',
13 |   FUTURES_WS_URL: 'wss://fstream.binance.com/ws',
14 |   
15 |   // API credentials
16 |   API_KEY: process.env.BINANCE_API_KEY || '',
17 |   API_SECRET: process.env.BINANCE_API_SECRET || '',
18 | 
19 |   // Constants
20 |   DEFAULT_ORDER_BOOK_LIMIT: 100,
21 |   DEFAULT_TRADE_LIMIT: 1000,
22 |   
23 |   // Rate limits
24 |   SPOT_RATE_LIMIT: 1200,
25 |   FUTURES_RATE_LIMIT: 1200,
26 |   
27 |   // WebSocket configurations
28 |   WS_PING_INTERVAL: 3 * 60 * 1000, // 3 minutes
29 |   WS_RECONNECT_DELAY: 5000,        // 5 seconds
30 |   WS_CONNECTION_TIMEOUT: 10000,     // 10 seconds
31 |   WS_MAX_RECONNECT_ATTEMPTS: 5,
32 |   
33 |   // HTTP configurations
34 |   HTTP_TIMEOUT: 10000,
35 |   HTTP_MAX_RETRIES: 3,
36 |   HTTP_RETRY_DELAY: 1000,
37 |   
38 |   ERRORS: {
39 |     RATE_LIMIT_EXCEEDED: 'Rate limit exceeded',
40 |     INVALID_SYMBOL: 'Invalid trading pair symbol',
41 |     WS_CONNECTION_ERROR: 'WebSocket connection error',
42 |     WS_SUBSCRIPTION_ERROR: 'WebSocket subscription error'
43 |   }
44 | } as const;
45 | 
46 | export type Config = typeof config;
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "binance-mcp-server",
 3 |   "version": "0.1.0",
 4 |   "description": "Binance market data provider with WebSocket support",
 5 |   "private": true,
 6 |   "type": "module",
 7 |   "bin": {
 8 |     "binance-mcp-server": "./build/index.js"
 9 |   },
10 |   "files": [
11 |     "build"
12 |   ],
13 |   "scripts": {
14 |     "build": "rimraf build && tsc -p tsconfig.json",
15 |     "postbuild": "chmod +x build/index.js",
16 |     "prepare": "npm run build",
17 |     "watch": "tsc --watch -p tsconfig.json",
18 |     "inspector": "npx @modelcontextprotocol/inspector build/index.js",
19 |     "start": "node build/index.js",
20 |     "test": "jest --config=jest.config.js",
21 |     "test:watch": "jest --watch --config=jest.config.js",
22 |     "test:coverage": "jest --coverage --config=jest.config.js",
23 |     "type-check": "tsc --noEmit -p tsconfig.test.json",
24 |     "lint": "eslint 'src/**/*.{js,ts}'"
25 |   },
26 |   "dependencies": {
27 |     "@modelcontextprotocol/sdk": "0.6.0",
28 |     "@types/ws": "^8.5.10",
29 |     "axios": "^1.6.7",
30 |     "ws": "^8.16.0",
31 |     "winston": "^3.11.0"
32 |   },
33 |   "devDependencies": {
34 |     "@jest/globals": "^29.7.0",
35 |     "@types/jest": "^29.5.12",
36 |     "@types/node": "^20.11.24",
37 |     "@typescript-eslint/eslint-plugin": "^7.1.0",
38 |     "@typescript-eslint/parser": "^7.1.0",
39 |     "eslint": "^8.57.0",
40 |     "jest": "^29.7.0",
41 |     "rimraf": "^5.0.5",
42 |     "ts-jest": "^29.1.2",
43 |     "typescript": "^5.3.3"
44 |   }
45 | }
```

--------------------------------------------------------------------------------
/src/types/api-types.ts:
--------------------------------------------------------------------------------

```typescript
 1 | export interface MarketDataParams {
 2 |   symbol: string;
 3 |   type: 'spot' | 'futures';
 4 | }
 5 | 
 6 | export interface KlineParams {
 7 |   symbol: string;
 8 |   type: 'spot' | 'futures';
 9 |   interval: string;
10 |   limit?: number;
11 | }
12 | 
13 | export interface StreamParams {
14 |   symbol: string;
15 |   type: 'spot' | 'futures';
16 |   streams: string[];
17 | }
18 | 
19 | export interface FuturesDataParams {
20 |   symbol: string;
21 | }
22 | 
23 | export class APIError extends Error {
24 |   constructor(message: string, public readonly cause?: Error) {
25 |     super(message);
26 |     this.name = 'APIError';
27 |   }
28 | }
29 | 
30 | // Type guards
31 | export function isMarketDataParams(params: any): params is MarketDataParams {
32 |   return (
33 |     typeof params === 'object' &&
34 |     typeof params.symbol === 'string' &&
35 |     (params.type === 'spot' || params.type === 'futures')
36 |   );
37 | }
38 | 
39 | export function isKlineParams(params: any): params is KlineParams {
40 |   return (
41 |     typeof params === 'object' &&
42 |     typeof params.symbol === 'string' &&
43 |     (params.type === 'spot' || params.type === 'futures') &&
44 |     typeof params.interval === 'string' &&
45 |     (params.limit === undefined || typeof params.limit === 'number')
46 |   );
47 | }
48 | 
49 | export function isStreamParams(params: any): params is StreamParams {
50 |   return (
51 |     typeof params === 'object' &&
52 |     typeof params.symbol === 'string' &&
53 |     (params.type === 'spot' || params.type === 'futures') &&
54 |     Array.isArray(params.streams)
55 |   );
56 | }
57 | 
58 | export function isFuturesDataParams(params: any): params is FuturesDataParams {
59 |   return (
60 |     typeof params === 'object' &&
61 |     typeof params.symbol === 'string'
62 |   );
63 | }
```

--------------------------------------------------------------------------------
/setup.sh:
--------------------------------------------------------------------------------

```bash
 1 | #!/bin/bash
 2 | 
 3 | # Colors for output
 4 | RED='\033[0;31m'
 5 | GREEN='\033[0;32m'
 6 | NC='\033[0m' # No Color
 7 | 
 8 | # Check if we're in the correct directory (has package.json)
 9 | if [ ! -f "package.json" ]; then
10 |     echo -e "${RED}Error: package.json not found. Are you in the correct directory?${NC}"
11 |     exit 1
12 | fi
13 | 
14 | # Function to create directory if it doesn't exist
15 | create_dir() {
16 |     if [ ! -d "$1" ]; then
17 |         mkdir -p "$1"
18 |         echo -e "${GREEN}Created directory: $1${NC}"
19 |     else
20 |         echo "Directory already exists: $1"
21 |     fi
22 | }
23 | 
24 | # Function to create file if it doesn't exist
25 | create_file() {
26 |     if [ ! -f "$1" ]; then
27 |         touch "$1"
28 |         # Add basic export statement to TypeScript files
29 |         if [[ $1 == *.ts ]]; then
30 |             echo "// $1" > "$1"
31 |             echo "export {}" >> "$1"
32 |         fi
33 |         echo -e "${GREEN}Created file: $1${NC}"
34 |     else
35 |         echo "File already exists: $1"
36 |     fi
37 | }
38 | 
39 | # Create directory structure
40 | create_dir "src/connectors"
41 | create_dir "src/types"
42 | create_dir "src/utils"
43 | 
44 | # Create files
45 | create_file "src/connectors/binance-rest.ts"
46 | create_file "src/connectors/binance-ws.ts"
47 | create_file "src/types/market-data.ts"
48 | create_file "src/types/api-types.ts"
49 | create_file "src/utils/logger.ts"
50 | create_file "src/config.ts"
51 | 
52 | echo -e "\n${GREEN}Directory structure created successfully!${NC}"
53 | echo -e "Next steps:"
54 | echo "1. Implement the TypeScript interfaces in types/"
55 | echo "2. Set up the configuration in config.ts"
56 | echo "3. Implement the connectors in connectors/"
57 | echo "4. Set up logging in utils/logger.ts"
58 | echo "5. Update main server implementation in index.ts"
59 | 
60 | # Print final directory tree
61 | echo -e "\nFinal directory structure:"
62 | tree
```

--------------------------------------------------------------------------------
/src/types/ws-stream.ts:
--------------------------------------------------------------------------------

```typescript
  1 | export type StreamEventType = 
  2 |   | 'trade'
  3 |   | 'ticker'
  4 |   | 'bookTicker'
  5 |   | 'kline'
  6 |   | 'depth'
  7 |   | 'forceOrder'    // Liquidation orders
  8 |   | 'markPrice'     // Mark price and funding rate
  9 |   | 'openInterest'; // Open interest updates
 10 | 
 11 | export interface WebSocketMessage<T> {
 12 |   stream: string;
 13 |   data: T;
 14 |   timestamp: number;
 15 | }
 16 | 
 17 | export type StreamEventData =
 18 |   | TradeData
 19 |   | TickerData
 20 |   | BookTickerData
 21 |   | KlineData
 22 |   | DepthData
 23 |   | ForceOrderData
 24 |   | MarkPriceData
 25 |   | OpenInterestData;
 26 | 
 27 | // Existing interfaces
 28 | export interface TradeData {
 29 |   e: 'trade';
 30 |   E: number;
 31 |   s: string;
 32 |   t: number;
 33 |   p: string;
 34 |   q: string;
 35 |   b: number;
 36 |   a: number;
 37 |   T: number;
 38 |   m: boolean;
 39 | }
 40 | 
 41 | export interface TickerData {
 42 |   e: '24hrTicker';
 43 |   E: number;
 44 |   s: string;
 45 |   p: string;
 46 |   P: string;
 47 |   w: string;
 48 |   c: string;
 49 |   Q: string;
 50 |   o: string;
 51 |   h: string;
 52 |   l: string;
 53 |   v: string;
 54 |   q: string;
 55 | }
 56 | 
 57 | export interface BookTickerData {
 58 |   e: 'bookTicker';
 59 |   u: number;
 60 |   s: string;
 61 |   b: string;
 62 |   B: string;
 63 |   a: string;
 64 |   A: string;
 65 | }
 66 | 
 67 | export interface KlineData {
 68 |   e: 'kline';
 69 |   E: number;
 70 |   s: string;
 71 |   k: {
 72 |     t: number;
 73 |     T: number;
 74 |     s: string;
 75 |     i: string;
 76 |     f: number;
 77 |     L: number;
 78 |     o: string;
 79 |     c: string;
 80 |     h: string;
 81 |     l: string;
 82 |     v: string;
 83 |     n: number;
 84 |     x: boolean;
 85 |     q: string;
 86 |     V: string;
 87 |     Q: string;
 88 |   };
 89 | }
 90 | 
 91 | export interface DepthData {
 92 |   e: 'depthUpdate';
 93 |   E: number;
 94 |   s: string;
 95 |   U: number;
 96 |   u: number;
 97 |   b: [string, string][];
 98 |   a: [string, string][];
 99 | }
100 | 
101 | // New Futures-specific interfaces
102 | export interface ForceOrderData {
103 |   e: 'forceOrder';
104 |   E: number;              // Event time
105 |   o: {
106 |     s: string;           // Symbol
107 |     S: 'SELL' | 'BUY';   // Side
108 |     o: 'LIMIT';          // Order type
109 |     f: 'IOC';           // Time in force
110 |     q: string;          // Original quantity
111 |     p: string;          // Price
112 |     ap: string;         // Average price
113 |     X: 'FILLED';        // Order status
114 |     l: string;          // Last filled quantity
115 |     z: string;          // Cumulative filled quantity
116 |     T: number;          // Trade time
117 |   };
118 | }
119 | 
120 | export interface MarkPriceData {
121 |   e: 'markPriceUpdate';
122 |   E: number;           // Event time
123 |   s: string;          // Symbol
124 |   p: string;          // Mark price
125 |   i: string;          // Index price
126 |   P: string;          // Estimated settle price
127 |   r: string;          // Funding rate
128 |   T: number;          // Next funding time
129 | }
130 | 
131 | export interface OpenInterestData {
132 |   e: 'openInterest';
133 |   E: number;          // Event time
134 |   s: string;          // Symbol
135 |   o: string;          // Open interest
136 |   T: number;          // Transaction time
137 | }
```

--------------------------------------------------------------------------------
/src/__tests__/binance-ws.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { BinanceWebSocketManager } from '../connectors/binance-ws';
  2 | import WebSocket from 'ws';
  3 | import { StreamEventData, TradeData } from '../types/ws-stream';
  4 | 
  5 | jest.mock('ws');
  6 | 
  7 | type MockWebSocket = jest.Mocked<WebSocket> & {
  8 |   readyState: number;
  9 | };
 10 | 
 11 | describe('BinanceWebSocketManager', () => {
 12 |   let wsManager: BinanceWebSocketManager;
 13 |   let mockWs: MockWebSocket;
 14 | 
 15 |   beforeEach(() => {
 16 |     wsManager = new BinanceWebSocketManager();
 17 |     mockWs = {
 18 |       readyState: WebSocket.CONNECTING,
 19 |       on: jest.fn(),
 20 |       once: jest.fn(),
 21 |       emit: jest.fn(),
 22 |       close: jest.fn(),
 23 |       ping: jest.fn(),
 24 |       send: jest.fn(),
 25 |       terminate: jest.fn(),
 26 |       removeListener: jest.fn(),
 27 |       removeAllListeners: jest.fn(),
 28 |       setMaxListeners: jest.fn(),
 29 |       getMaxListeners: jest.fn(),
 30 |       listeners: jest.fn(),
 31 |       rawListeners: jest.fn(),
 32 |       listenerCount: jest.fn(),
 33 |       eventNames: jest.fn(),
 34 |       addListener: jest.fn(),
 35 |       off: jest.fn(),
 36 |       prependListener: jest.fn(),
 37 |       prependOnceListener: jest.fn(),
 38 |     } as unknown as MockWebSocket;
 39 | 
 40 |     (WebSocket as unknown as jest.Mock).mockImplementation(() => mockWs);
 41 |   });
 42 | 
 43 |   afterEach(() => {
 44 |     wsManager.close();
 45 |     jest.clearAllMocks();
 46 |   });
 47 | 
 48 |   it('should successfully subscribe to stream', () => {
 49 |     const symbol = 'BTCUSDT';
 50 |     const streams = ['trade', 'ticker'] as const;
 51 | 
 52 |     wsManager.subscribe(symbol, 'spot', streams);
 53 | 
 54 |     expect(WebSocket).toHaveBeenCalledWith(
 55 |       expect.stringContaining(`btcusdt@trade/btcusdt@ticker`)
 56 |     );
 57 |   });
 58 | 
 59 |   it('should handle incoming messages correctly', (done) => {
 60 |     const symbol = 'BTCUSDT';
 61 |     const mockData = {
 62 |       stream: 'btcusdt@trade',
 63 |       data: {
 64 |         e: 'trade',
 65 |         E: 123456789,
 66 |         s: 'BTCUSDT',
 67 |         p: '50000.00',
 68 |         q: '1.0'
 69 |       } as TradeData
 70 |     };
 71 | 
 72 |     wsManager.subscribe(symbol, 'spot', ['trade']);
 73 |     wsManager.onStreamData(symbol, 'trade', (data: StreamEventData) => {
 74 |       expect(data).toEqual(mockData.data);
 75 |       done();
 76 |     });
 77 | 
 78 |     // Simulate receiving a message
 79 |     mockWs.emit('message', JSON.stringify(mockData));
 80 |   });
 81 | 
 82 |   it('should handle reconnection on connection close', () => {
 83 |     const symbol = 'BTCUSDT';
 84 |     wsManager.subscribe(symbol, 'spot', ['trade']);
 85 | 
 86 |     // Simulate connection close
 87 |     mockWs.emit('close');
 88 | 
 89 |     // Verify that a new connection attempt is made
 90 |     expect(WebSocket).toHaveBeenCalledTimes(2);
 91 |   });
 92 | 
 93 |   it('should clean up resources on unsubscribe', () => {
 94 |     const symbol = 'BTCUSDT';
 95 |     wsManager.subscribe(symbol, 'spot', ['trade']);
 96 |     wsManager.unsubscribe(symbol);
 97 | 
 98 |     expect(mockWs.close).toHaveBeenCalled();
 99 |   });
100 | 
101 |   it('should handle multiple stream subscriptions', () => {
102 |     const symbol = 'BTCUSDT';
103 |     const streams = ['trade', 'ticker', 'bookTicker'] as const;
104 | 
105 |     wsManager.subscribe(symbol, 'spot', streams);
106 | 
107 |     expect(WebSocket).toHaveBeenCalledWith(
108 |       expect.stringContaining('btcusdt@trade/btcusdt@ticker/btcusdt@bookTicker')
109 |     );
110 |   });
111 | 
112 |   it('should properly maintain connection state', () => {
113 |     const symbol = 'BTCUSDT';
114 |     wsManager.subscribe(symbol, 'spot', ['trade']);
115 | 
116 |     // Update mockWs.readyState which is now properly typed
117 |     mockWs.readyState = WebSocket.OPEN;
118 |     mockWs.emit('open');
119 | 
120 |     expect(wsManager.getConnectionState(symbol)).toBe(WebSocket.OPEN);
121 |   });
122 | });
```

--------------------------------------------------------------------------------
/src/connectors/binance-ws.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import WebSocket from 'ws';
  2 | import { config } from '../config.js';
  3 | import { logger } from '../utils/logger.js';
  4 | import {
  5 |   WebSocketMessage,
  6 |   StreamEventType,
  7 |   StreamEventData,
  8 |   TradeData,
  9 |   TickerData,
 10 |   BookTickerData,
 11 |   KlineData,
 12 |   ForceOrderData,
 13 |   MarkPriceData,
 14 |   OpenInterestData
 15 | } from '../types/ws-stream.js';
 16 | 
 17 | type WSReadyState = number;
 18 | 
 19 | interface StreamSubscription {
 20 |   symbol: string;
 21 |   type: 'spot' | 'futures';
 22 |   streams: StreamEventType[];
 23 |   reconnectAttempts: number;
 24 |   reconnectTimeout?: NodeJS.Timeout;
 25 | }
 26 | 
 27 | type MessageHandler = (data: StreamEventData) => void;
 28 | 
 29 | export class BinanceWebSocketManager {
 30 |   private connections: Map<string, WebSocket>;
 31 |   private pingIntervals: Map<string, NodeJS.Timeout>;
 32 |   private messageCallbacks: Map<string, Map<StreamEventType, MessageHandler[]>>;
 33 |   private subscriptions: Map<string, StreamSubscription>;
 34 |   private readonly MAX_RECONNECT_ATTEMPTS = 5;
 35 |   private readonly RECONNECT_DELAY = config.WS_RECONNECT_DELAY || 5000;
 36 | 
 37 |   constructor() {
 38 |     this.connections = new Map();
 39 |     this.pingIntervals = new Map();
 40 |     this.messageCallbacks = new Map();
 41 |     this.subscriptions = new Map();
 42 |   }
 43 | 
 44 |   public subscribe(symbol: string, type: 'spot' | 'futures', streams: StreamEventType[]): void {
 45 |     const subscription: StreamSubscription = {
 46 |       symbol,
 47 |       type,
 48 |       streams,
 49 |       reconnectAttempts: 0
 50 |     };
 51 |     
 52 |     if (!this.messageCallbacks.has(symbol)) {
 53 |       this.messageCallbacks.set(symbol, new Map());
 54 |     }
 55 |     
 56 |     const symbolCallbacks = this.messageCallbacks.get(symbol)!;
 57 |     streams.forEach(stream => {
 58 |       if (!symbolCallbacks.has(stream)) {
 59 |         symbolCallbacks.set(stream, []);
 60 |       }
 61 |     });
 62 | 
 63 |     this.subscriptions.set(symbol, subscription);
 64 |     this.connectWebSocket(subscription);
 65 |   }
 66 | 
 67 |   private connectWebSocket(subscription: StreamSubscription): void {
 68 |     const { symbol, type, streams } = subscription;
 69 |     const wsUrl = type === 'spot' ? config.SPOT_WS_URL : config.FUTURES_WS_URL;
 70 |     
 71 |     // Handle special futures streams
 72 |     const streamNames = streams.map(stream => {
 73 |       if (type === 'futures') {
 74 |         switch (stream) {
 75 |           case 'forceOrder':
 76 |             return `${symbol.toLowerCase()}@forceOrder`;
 77 |           case 'markPrice':
 78 |             return `${symbol.toLowerCase()}@markPrice@1s`; // 1s update frequency
 79 |           case 'openInterest':
 80 |             return `${symbol.toLowerCase()}@openInterest@1s`;
 81 |           default:
 82 |             return `${symbol.toLowerCase()}@${stream}`;
 83 |         }
 84 |       }
 85 |       return `${symbol.toLowerCase()}@${stream}`;
 86 |     });
 87 |     
 88 |     try {
 89 |       const ws = new WebSocket(`${wsUrl}/${streamNames.join('/')}`);
 90 |       
 91 |       ws.on('open', () => {
 92 |         logger.info(`WebSocket connected for ${symbol} ${streams.join(', ')}`);
 93 |         subscription.reconnectAttempts = 0;
 94 |         this.setupPingInterval(symbol, ws);
 95 |       });
 96 | 
 97 |       ws.on('message', (data: WebSocket.Data) => {
 98 |         try {
 99 |           const message = JSON.parse(data.toString()) as WebSocketMessage<StreamEventData>;
100 |           this.handleStreamMessage(symbol, message);
101 |         } catch (error) {
102 |           logger.error('Error parsing WebSocket message:', error);
103 |         }
104 |       });
105 | 
106 |       ws.on('error', (error: Error) => {
107 |         logger.error(`WebSocket error for ${symbol}:`, error);
108 |       });
109 | 
110 |       ws.on('close', () => {
111 |         logger.info(`WebSocket closed for ${symbol}`);
112 |         this.cleanup(symbol);
113 |         this.handleReconnection(subscription);
114 |       });
115 | 
116 |       ws.on('pong', () => {
117 |         logger.debug(`Received pong from ${symbol} WebSocket`);
118 |       });
119 | 
120 |       this.connections.set(symbol, ws);
121 |     } catch (error) {
122 |       logger.error(`Error creating WebSocket connection for ${symbol}:`, error);
123 |       this.handleReconnection(subscription);
124 |       throw error;
125 |     }
126 |   }
127 | 
128 |   private handleStreamMessage(symbol: string, message: WebSocketMessage<StreamEventData>): void {
129 |     const symbolCallbacks = this.messageCallbacks.get(symbol);
130 |     if (!symbolCallbacks) return;
131 | 
132 |     // Extract stream type from the stream name
133 |     const streamParts = message.stream.split('@');
134 |     if (streamParts.length < 2) return;
135 | 
136 |     let streamType = streamParts[1] as StreamEventType;
137 |     // Handle special cases where the stream name has additional parts (e.g., markPrice@1s)
138 |     if (streamParts.length > 2) {
139 |       streamType = streamParts[1].split('@')[0] as StreamEventType;
140 |     }
141 | 
142 |     const handlers = symbolCallbacks.get(streamType);
143 |     
144 |     if (handlers) {
145 |       handlers.forEach(handler => {
146 |         try {
147 |           handler(message.data);
148 |         } catch (error) {
149 |           logger.error(`Error in message handler for ${symbol} ${streamType}:`, error);
150 |         }
151 |       });
152 |     }
153 |   }
154 | 
155 |   public onStreamData(symbol: string, streamType: StreamEventType, handler: MessageHandler): void {
156 |     const symbolCallbacks = this.messageCallbacks.get(symbol);
157 |     if (!symbolCallbacks) {
158 |       logger.error(`No callbacks registered for symbol ${symbol}`);
159 |       return;
160 |     }
161 | 
162 |     const handlers = symbolCallbacks.get(streamType) || [];
163 |     handlers.push(handler);
164 |     symbolCallbacks.set(streamType, handlers);
165 |   }
166 | 
167 |   private handleReconnection(subscription: StreamSubscription): void {
168 |     const { symbol, reconnectAttempts } = subscription;
169 | 
170 |     if (reconnectAttempts >= this.MAX_RECONNECT_ATTEMPTS) {
171 |       logger.error(`Max reconnection attempts reached for ${symbol}`);
172 |       return;
173 |     }
174 | 
175 |     subscription.reconnectAttempts++;
176 |     const delay = this.RECONNECT_DELAY * Math.pow(2, reconnectAttempts - 1); // Exponential backoff
177 | 
178 |     logger.info(`Attempting to reconnect ${symbol} in ${delay}ms (attempt ${reconnectAttempts})`);
179 | 
180 |     subscription.reconnectTimeout = setTimeout(() => {
181 |       this.connectWebSocket(subscription);
182 |     }, delay);
183 |   }
184 | 
185 |   private setupPingInterval(symbol: string, ws: WebSocket): void {
186 |     const interval = setInterval(() => {
187 |       if (ws.readyState === WebSocket.OPEN) {
188 |         ws.ping((error?: Error) => {
189 |           if (error) {
190 |             logger.error(`Error sending ping for ${symbol}:`, error);
191 |           }
192 |         });
193 |       }
194 |     }, config.WS_PING_INTERVAL);
195 |     this.pingIntervals.set(symbol, interval);
196 |   }
197 | 
198 |   private cleanup(symbol: string): void {
199 |     const interval = this.pingIntervals.get(symbol);
200 |     if (interval) {
201 |       clearInterval(interval);
202 |       this.pingIntervals.delete(symbol);
203 |     }
204 | 
205 |     const subscription = this.subscriptions.get(symbol);
206 |     if (subscription?.reconnectTimeout) {
207 |       clearTimeout(subscription.reconnectTimeout);
208 |     }
209 | 
210 |     this.connections.delete(symbol);
211 |   }
212 | 
213 |   public unsubscribe(symbol: string): void {
214 |     const ws = this.connections.get(symbol);
215 |     if (ws) {
216 |       ws.close();
217 |     }
218 |     this.cleanup(symbol);
219 |     this.subscriptions.delete(symbol);
220 |     this.messageCallbacks.delete(symbol);
221 |   }
222 | 
223 |   public close(): void {
224 |     this.connections.forEach((ws, symbol) => {
225 |       ws.close();
226 |       this.cleanup(symbol);
227 |     });
228 |     this.subscriptions.clear();
229 |     this.messageCallbacks.clear();
230 |   }
231 | 
232 |   public getConnectionState(symbol: string): WSReadyState | undefined {
233 |     const ws = this.connections.get(symbol);
234 |     return ws?.readyState;
235 |   }
236 | 
237 |   public isSubscribed(symbol: string, streamType: StreamEventType): boolean {
238 |     const subscription = this.subscriptions.get(symbol);
239 |     return subscription?.streams.includes(streamType) || false;
240 |   }
241 | }
```

--------------------------------------------------------------------------------
/src/connectors/binance-rest.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import axios, { AxiosInstance } from 'axios';
  2 | import { config } from '../config.js';
  3 | import { logger } from '../utils/logger.js';
  4 | import { APIError } from '../types/api-types.js';
  5 | 
  6 | export class BinanceRestConnector {
  7 |   private readonly axiosInstance: AxiosInstance;
  8 |   private readonly retryDelay = config.HTTP_RETRY_DELAY;
  9 |   private readonly maxRetries = config.HTTP_MAX_RETRIES;
 10 | 
 11 |   constructor() {
 12 |     this.axiosInstance = axios.create({
 13 |       timeout: config.HTTP_TIMEOUT,
 14 |       headers: {
 15 |         'Content-Type': 'application/json'
 16 |       }
 17 |     });
 18 |     logger.info('BinanceRestConnector initialized');
 19 |     logger.info(`Futures REST URL: ${config.FUTURES_REST_URL}`);
 20 |   }
 21 | 
 22 |   private async executeWithRetry<T>(operation: () => Promise<T>, retries = 0): Promise<T> {
 23 |     try {
 24 |       return await operation();
 25 |     } catch (error) {
 26 |       if (retries >= this.maxRetries) {
 27 |         throw error;
 28 |       }
 29 | 
 30 |       const delay = this.retryDelay * Math.pow(2, retries);
 31 |       logger.warn(`Request failed, retrying in ${delay}ms...`);
 32 |       
 33 |       await new Promise(resolve => setTimeout(resolve, delay));
 34 |       return this.executeWithRetry(operation, retries + 1);
 35 |     }
 36 |   }
 37 | 
 38 |   public async getMarketData(symbol: string, type: 'spot' | 'futures'): Promise<any> {
 39 |     try {
 40 |       logger.info(`Getting ${type} market data for ${symbol}`);
 41 | 
 42 |       if (type === 'spot') {
 43 |         const data = await this.executeWithRetry(() =>
 44 |           this.axiosInstance.get(`${config.SPOT_REST_URL}/ticker/24hr`, {
 45 |             params: { symbol: symbol.toUpperCase() }
 46 |           }).then(response => response.data)
 47 |         );
 48 |         logger.info('Successfully fetched spot market data');
 49 |         return data;
 50 |       }
 51 | 
 52 |       // For futures, fetch all relevant data in parallel
 53 |       logger.info('Fetching futures data from multiple endpoints...');
 54 |       
 55 |       try {
 56 |         const [
 57 |           marketData,
 58 |           openInterest,
 59 |           fundingData,
 60 |           liquidations
 61 |         ] = await Promise.all([
 62 |           // Basic market data
 63 |           this.executeWithRetry(() =>
 64 |             this.axiosInstance.get(`${config.FUTURES_REST_URL}/ticker/24hr`, {
 65 |               params: { symbol: symbol.toUpperCase() }
 66 |             }).then(response => {
 67 |               logger.info('Successfully fetched futures ticker data');
 68 |               return response.data;
 69 |             })
 70 |           ),
 71 |           // Open interest
 72 |           this.executeWithRetry(() =>
 73 |             this.axiosInstance.get(`${config.FUTURES_REST_URL}/openInterest`, {
 74 |               params: { symbol: symbol.toUpperCase() }
 75 |             }).then(response => {
 76 |               logger.info('Successfully fetched open interest data');
 77 |               return response.data;
 78 |             })
 79 |           ),
 80 |           // Premium index (funding rate)
 81 |           this.executeWithRetry(() =>
 82 |             this.axiosInstance.get(`${config.FUTURES_REST_URL}/premiumIndex`, {
 83 |               params: {
 84 |                 symbol: symbol.toUpperCase()
 85 |               }
 86 |             }).then(response => {
 87 |               logger.info('Successfully fetched funding rate data');
 88 |               return response.data;
 89 |             })
 90 |           ),
 91 |           // Recent liquidations
 92 |           this.executeWithRetry(() =>
 93 |             this.axiosInstance.get(`${config.FUTURES_REST_URL}/forceOrders`, {
 94 |               params: {
 95 |                 symbol: symbol.toUpperCase(),
 96 |                 startTime: Date.now() - 24 * 60 * 60 * 1000,
 97 |                 limit: 100
 98 |               }
 99 |             }).then(response => {
100 |               logger.info('Successfully fetched liquidations data');
101 |               return response.data;
102 |             })
103 |           )
104 |         ]);
105 | 
106 |         logger.info('Successfully fetched all futures data, combining responses...');
107 | 
108 |         // Combine all futures data with correct field mappings
109 |         const combinedData = {
110 |           ...marketData,
111 |           openInterest: openInterest.openInterest,
112 |           fundingRate: fundingData.lastFundingRate,
113 |           markPrice: fundingData.markPrice,
114 |           nextFundingTime: fundingData.nextFundingTime,
115 |           liquidations24h: liquidations.length,
116 |           liquidationVolume24h: liquidations.reduce((sum: number, order: any) => 
117 |             sum + parseFloat(order.executedQty), 0
118 |           )
119 |         };
120 | 
121 |         logger.info('Successfully combined futures data');
122 |         return combinedData;
123 | 
124 |       } catch (error) {
125 |         logger.error('Error in futures data Promise.all:', error);
126 |         throw error;
127 |       }
128 | 
129 |     } catch (error) {
130 |       logger.error('Error fetching market data:', error);
131 |       throw new APIError('Failed to fetch market data', error as Error);
132 |     }
133 |   }
134 | 
135 |   public async getFuturesOpenInterest(symbol: string): Promise<any> {
136 |     try {
137 |       logger.info(`Getting futures open interest for ${symbol}`);
138 |       const response = await this.executeWithRetry(() =>
139 |         this.axiosInstance.get(`${config.FUTURES_REST_URL}/openInterest`, {
140 |           params: { symbol: symbol.toUpperCase() }
141 |         })
142 |       );
143 |       logger.info('Successfully fetched open interest data');
144 |       return response.data;
145 |     } catch (error) {
146 |       logger.error('Error fetching open interest:', error);
147 |       throw new APIError('Failed to fetch open interest data', error as Error);
148 |     }
149 |   }
150 | 
151 |   public async getFuturesFundingRate(symbol: string): Promise<any> {
152 |     try {
153 |       logger.info(`Getting futures funding rate for ${symbol}`);
154 |       const response = await this.executeWithRetry(() =>
155 |         this.axiosInstance.get(`${config.FUTURES_REST_URL}/premiumIndex`, {
156 |           params: {
157 |             symbol: symbol.toUpperCase()
158 |           }
159 |         })
160 |       );
161 |       logger.info('Successfully fetched funding rate data');
162 |       return response.data;
163 |     } catch (error) {
164 |       logger.error('Error fetching funding rate:', error);
165 |       throw new APIError('Failed to fetch funding rate data', error as Error);
166 |     }
167 |   }
168 | 
169 |   public async getFuturesLiquidations(symbol: string): Promise<any> {
170 |     try {
171 |       logger.info(`Getting futures liquidations for ${symbol}`);
172 |       const response = await this.executeWithRetry(() =>
173 |         this.axiosInstance.get(`${config.FUTURES_REST_URL}/forceOrders`, {
174 |           params: {
175 |             symbol: symbol.toUpperCase(),
176 |             startTime: Date.now() - 24 * 60 * 60 * 1000,
177 |             limit: 1000
178 |           }
179 |         })
180 |       );
181 |       logger.info('Successfully fetched liquidations data');
182 |       return response.data;
183 |     } catch (error) {
184 |       logger.error('Error fetching liquidations:', error);
185 |       throw new APIError('Failed to fetch liquidations data', error as Error);
186 |     }
187 |   }
188 | 
189 |   public async getKlines(
190 |     symbol: string,
191 |     type: 'spot' | 'futures',
192 |     interval: string,
193 |     limit?: number
194 |   ): Promise<any> {
195 |     try {
196 |       logger.info(`Getting ${type} klines for ${symbol}`);
197 |       const baseUrl = type === 'spot' ? config.SPOT_REST_URL : config.FUTURES_REST_URL;
198 |       const response = await this.executeWithRetry(() =>
199 |         this.axiosInstance.get(`${baseUrl}/klines`, {
200 |           params: {
201 |             symbol: symbol.toUpperCase(),
202 |             interval,
203 |             limit: limit || 500
204 |           }
205 |         })
206 |       );
207 |       logger.info('Successfully fetched klines data');
208 |       return response.data;
209 |     } catch (error) {
210 |       logger.error('Error fetching klines:', error);
211 |       throw new APIError('Failed to fetch klines data', error as Error);
212 |     }
213 |   }
214 | 
215 |   public async getExchangeInfo(type: 'spot' | 'futures'): Promise<any> {
216 |     try {
217 |       logger.info(`Getting ${type} exchange info`);
218 |       const baseUrl = type === 'spot' ? config.SPOT_REST_URL : config.FUTURES_REST_URL;
219 |       const response = await this.executeWithRetry(() =>
220 |         this.axiosInstance.get(`${baseUrl}/exchangeInfo`)
221 |       );
222 |       logger.info('Successfully fetched exchange info');
223 |       return response.data;
224 |     } catch (error) {
225 |       logger.error('Error fetching exchange info:', error);
226 |       throw new APIError('Failed to fetch exchange info', error as Error);
227 |     }
228 |   }
229 | }
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
  1 | #!/usr/bin/env node
  2 | 
  3 | import { Server } from "@modelcontextprotocol/sdk/server/index.js";
  4 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
  5 | import {
  6 |   CallToolRequestSchema,
  7 |   ListToolsRequestSchema,
  8 | } from "@modelcontextprotocol/sdk/types.js";
  9 | 
 10 | import { BinanceWebSocketManager } from './connectors/binance-ws.js';
 11 | import { BinanceRestConnector } from './connectors/binance-rest.js';
 12 | import { config } from './config.js';
 13 | import { logger } from './utils/logger.js';
 14 | import { 
 15 |   MarketDataParams, 
 16 |   KlineParams, 
 17 |   StreamParams, 
 18 |   FuturesDataParams,
 19 |   APIError,
 20 |   isMarketDataParams,
 21 |   isKlineParams,
 22 |   isStreamParams,
 23 |   isFuturesDataParams
 24 | } from './types/api-types.js';
 25 | import { StreamEventData } from './types/ws-stream.js';
 26 | 
 27 | const wsManager = new BinanceWebSocketManager();
 28 | const restConnector = new BinanceRestConnector();
 29 | 
 30 | const server = new Server(
 31 |   {
 32 |     name: config.NAME,
 33 |     version: config.VERSION,
 34 |     description: 'Binance market data provider with WebSocket support'
 35 |   },
 36 |   {
 37 |     capabilities: {
 38 |       tools: {},
 39 |     },
 40 |   }
 41 | );
 42 | 
 43 | // List available tools
 44 | server.setRequestHandler(ListToolsRequestSchema, async () => {
 45 |   return {
 46 |     tools: [
 47 |       {
 48 |         name: "get_market_data",
 49 |         description: "Get comprehensive market data for a trading pair",
 50 |         inputSchema: {
 51 |           type: "object",
 52 |           properties: {
 53 |             symbol: { 
 54 |               type: "string", 
 55 |               description: "Trading pair symbol (e.g., BTCUSDT)" 
 56 |             },
 57 |             type: { 
 58 |               type: "string", 
 59 |               enum: ["spot", "futures"], 
 60 |               description: "Market type" 
 61 |             }
 62 |           },
 63 |           required: ["symbol", "type"]
 64 |         }
 65 |       },
 66 |       {
 67 |         name: "test_futures_endpoints",
 68 |         description: "Test individual futures endpoints",
 69 |         inputSchema: {
 70 |           type: "object",
 71 |           properties: {
 72 |             symbol: { 
 73 |               type: "string", 
 74 |               description: "Trading pair symbol (e.g., BTCUSDT)" 
 75 |             }
 76 |           },
 77 |           required: ["symbol"]
 78 |         }
 79 |       },
 80 |       {
 81 |         name: "get_futures_open_interest",
 82 |         description: "Get current open interest for a futures trading pair",
 83 |         inputSchema: {
 84 |           type: "object",
 85 |           properties: {
 86 |             symbol: { 
 87 |               type: "string", 
 88 |               description: "Trading pair symbol (e.g., BTCUSDT)" 
 89 |             }
 90 |           },
 91 |           required: ["symbol"]
 92 |         }
 93 |       },
 94 |       {
 95 |         name: "get_futures_funding_rate",
 96 |         description: "Get current funding rate for a futures trading pair",
 97 |         inputSchema: {
 98 |           type: "object",
 99 |           properties: {
100 |             symbol: { 
101 |               type: "string", 
102 |               description: "Trading pair symbol (e.g., BTCUSDT)" 
103 |             }
104 |           },
105 |           required: ["symbol"]
106 |         }
107 |       },
108 |       {
109 |         name: "get_klines",
110 |         description: "Get historical candlestick data",
111 |         inputSchema: {
112 |           type: "object",
113 |           properties: {
114 |             symbol: { 
115 |               type: "string", 
116 |               description: "Trading pair symbol (e.g., BTCUSDT)" 
117 |             },
118 |             type: { 
119 |               type: "string", 
120 |               enum: ["spot", "futures"], 
121 |               description: "Market type" 
122 |             },
123 |             interval: {
124 |               type: "string",
125 |               enum: ["1m", "5m", "15m", "30m", "1h", "4h", "1d", "1w", "1M"],
126 |               description: "Kline/candlestick chart interval"
127 |             },
128 |             limit: {
129 |               type: "number",
130 |               description: "Number of klines to retrieve (default 500, max 1000)"
131 |             }
132 |           },
133 |           required: ["symbol", "type", "interval"]
134 |         }
135 |       },
136 |       {
137 |         name: "subscribe_market_data",
138 |         description: "Subscribe to real-time market data updates",
139 |         inputSchema: {
140 |           type: "object",
141 |           properties: {
142 |             symbol: { 
143 |               type: "string", 
144 |               description: "Trading pair symbol (e.g., BTCUSDT)" 
145 |             },
146 |             type: { 
147 |               type: "string", 
148 |               enum: ["spot", "futures"], 
149 |               description: "Market type" 
150 |             },
151 |             streams: {
152 |               type: "array",
153 |               items: {
154 |                 type: "string",
155 |                 enum: ["ticker", "trade", "kline", "depth", "forceOrder", "markPrice", "openInterest"]
156 |               },
157 |               description: "List of data streams to subscribe to"
158 |             }
159 |           },
160 |           required: ["symbol", "type", "streams"]
161 |         }
162 |       }
163 |     ]
164 |   };
165 | });
166 | 
167 | // Handle tool calls
168 | server.setRequestHandler(CallToolRequestSchema, async (request) => {
169 |   try {
170 |     switch (request.params.name) {
171 |       case "get_market_data": {
172 |         if (!isMarketDataParams(request.params.arguments)) {
173 |           throw new Error('Invalid market data parameters');
174 |         }
175 |         const { symbol, type } = request.params.arguments;
176 |         const data = await restConnector.getMarketData(symbol, type);
177 |         return {
178 |           content: [{
179 |             type: "text",
180 |             text: JSON.stringify(data, null, 2)
181 |           }]
182 |         };
183 |       }
184 | 
185 |       case "test_futures_endpoints": {
186 |         if (!isFuturesDataParams(request.params.arguments)) {
187 |           throw new Error('Invalid futures data parameters');
188 |         }
189 |         const { symbol } = request.params.arguments;
190 |         
191 |         // Test each endpoint individually
192 |         const openInterest = await restConnector.getFuturesOpenInterest(symbol);
193 |         const fundingRate = await restConnector.getFuturesFundingRate(symbol);
194 |         const liquidations = await restConnector.getFuturesLiquidations(symbol);
195 | 
196 |         // Return all test results
197 |         return {
198 |           content: [{
199 |             type: "text",
200 |             text: JSON.stringify({
201 |               openInterest,
202 |               fundingRate,
203 |               liquidations
204 |             }, null, 2)
205 |           }]
206 |         };
207 |       }
208 | 
209 |       case "get_futures_open_interest": {
210 |         if (!isFuturesDataParams(request.params.arguments)) {
211 |           throw new Error('Invalid futures data parameters');
212 |         }
213 |         const { symbol } = request.params.arguments;
214 |         const data = await restConnector.getFuturesOpenInterest(symbol);
215 |         return {
216 |           content: [{
217 |             type: "text",
218 |             text: JSON.stringify(data, null, 2)
219 |           }]
220 |         };
221 |       }
222 | 
223 |       case "get_futures_funding_rate": {
224 |         if (!isFuturesDataParams(request.params.arguments)) {
225 |           throw new Error('Invalid futures data parameters');
226 |         }
227 |         const { symbol } = request.params.arguments;
228 |         const data = await restConnector.getFuturesFundingRate(symbol);
229 |         return {
230 |           content: [{
231 |             type: "text",
232 |             text: JSON.stringify(data, null, 2)
233 |           }]
234 |         };
235 |       }
236 | 
237 |       case "get_klines": {
238 |         if (!isKlineParams(request.params.arguments)) {
239 |           throw new Error('Invalid kline parameters');
240 |         }
241 |         const { symbol, type, interval, limit } = request.params.arguments;
242 |         const data = await restConnector.getKlines(symbol, type, interval, limit);
243 |         return {
244 |           content: [{
245 |             type: "text",
246 |             text: JSON.stringify(data, null, 2)
247 |           }]
248 |         };
249 |       }
250 | 
251 |       case "subscribe_market_data": {
252 |         if (!isStreamParams(request.params.arguments)) {
253 |           throw new Error('Invalid stream parameters');
254 |         }
255 |         const { symbol, type, streams } = request.params.arguments;
256 |         wsManager.subscribe(symbol, type, streams);
257 |         
258 |         // Set up message handler
259 |         wsManager.onStreamData(symbol, streams[0], (data: StreamEventData) => {
260 |           // Handle real-time data updates
261 |           logger.info(`Received WebSocket data for ${symbol}:`, data);
262 |         });
263 | 
264 |         return {
265 |           content: [{
266 |             type: "text",
267 |             text: `Successfully subscribed to ${streams.join(", ")} for ${symbol}`
268 |           }]
269 |         };
270 |       }
271 | 
272 |       default:
273 |         throw new Error(`Unknown tool: ${request.params.name}`);
274 |     }
275 |   } catch (error) {
276 |     const apiError = error as APIError;
277 |     logger.error('Error handling tool request:', apiError);
278 |     throw apiError;
279 |   }
280 | });
281 | 
282 | // Start the server
283 | async function main() {
284 |   const transport = new StdioServerTransport();
285 |   await server.connect(transport);
286 |   logger.info('Binance MCP server started successfully');
287 | }
288 | 
289 | // Handle cleanup on shutdown
290 | process.on('SIGINT', () => {
291 |   logger.info('Shutting down server...');
292 |   wsManager.close();
293 |   process.exit(0);
294 | });
295 | 
296 | main().catch((error) => {
297 |   logger.error('Failed to start server:', error);
298 |   process.exit(1);
299 | });
```