#
tokens: 9737/50000 16/16 files
lines: off (toggle) GitHub
raw markdown copy
# 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:
--------------------------------------------------------------------------------

```
node_modules/
build/
*.log
.env*
```

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

```markdown
# Binance MCP Server

A Model Context Protocol (MCP) server implementation for Binance market data with WebSocket support.

## Features

- Real-time market data streaming via WebSocket
- Support for both spot and futures markets
- Automatic reconnection with exponential backoff
- Type-safe message handling
- Comprehensive error handling

## Installation

```bash
npm install
```

## Usage

### Starting the Server

```bash
npm start
```

### WebSocket Stream Types

The following stream types are supported:

- `trade`: Real-time trade data
- `ticker`: 24-hour rolling window price change statistics
- `bookTicker`: Best bid/ask price and quantity
- `kline`: Candlestick data
- `markPrice`: Mark price and funding rate (futures only)
- `fundingRate`: Funding rate data (futures only)

### Example Usage in Claude Desktop

```typescript
// Subscribe to trade and ticker streams for BTC/USDT
await server.subscribe('BTCUSDT', 'spot', ['trade', 'ticker']);

// Handle incoming data
server.onStreamData('BTCUSDT', 'trade', (data) => {
  console.log('New trade:', data);
});
```

## Development

### Running Tests

```bash
npm test
```

### Building

```bash
npm run build
```

## License

Private

```

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

```json
{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "types": ["jest", "node"],
    "isolatedModules": false
  },
  "include": ["src/**/*.ts"],
  "exclude": ["node_modules"]
}

```

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

```javascript
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
  moduleNameMapper: {
    '^@/(.*)$': '<rootDir>/src/$1'
  },
  testMatch: ['**/__tests__/**/*.test.ts'],
  transform: {
    '^.+\\.tsx?$': ['ts-jest', {
      tsconfig: 'tsconfig.test.json'
    }]
  },
  moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node']
};
```

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

```typescript
import winston from 'winston';

export const logger = winston.createLogger({
  level: 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.json()
  ),
  transports: [
    new winston.transports.File({ filename: 'error.log', level: 'error' }),
    new winston.transports.File({ filename: 'combined.log' }),
    new winston.transports.Console({
      format: winston.format.combine(
        winston.format.colorize(),
        winston.format.simple()
      )
    })
  ]
});
```

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

```json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",        // Changed from ES2022
    "moduleResolution": "NodeNext",
    "outDir": "./build",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "declaration": true,
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    }
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "**/*.test.ts", "build"]
}
```

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

```typescript
export interface MarketData {
  symbol: string;
  exchange: string;
  type: 'spot' | 'futures';
  price: number;
  timestamp: number;
  volume24h: number;
  volumeDelta24h?: number;
  priceChange24h: number;
  priceChange1h?: number;
  price24hHigh: number;
  price24hLow: number;
  tradeCount24h: number;
  bidAskSpread?: number;
  openInterest?: number;
  fundingRate?: number;
  liquidations24h?: number;
}

export interface OrderBookData {
  symbol: string;
  type: 'spot' | 'futures';
  bids: [number, number][];
  asks: [number, number][];
  timestamp: number;
  lastUpdateId: number;
}

export interface TradeData {
  symbol: string;
  type: 'spot' | 'futures';
  price: number;
  quantity: number;
  timestamp: number;
  isBuyerMaker: boolean;
  tradeId: number;
}
```

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

```typescript
export const config = {

  // Server config
  NAME: 'binance-market-data',
  VERSION: '1.0.0',
  
  // REST endpoints
  SPOT_REST_URL: 'https://api.binance.com/api/v3',
  FUTURES_REST_URL: 'https://fapi.binance.com/fapi/v1',
  
  // WebSocket endpoints
  SPOT_WS_URL: 'wss://stream.binance.com:9443/ws',
  FUTURES_WS_URL: 'wss://fstream.binance.com/ws',
  
  // API credentials
  API_KEY: process.env.BINANCE_API_KEY || '',
  API_SECRET: process.env.BINANCE_API_SECRET || '',

  // Constants
  DEFAULT_ORDER_BOOK_LIMIT: 100,
  DEFAULT_TRADE_LIMIT: 1000,
  
  // Rate limits
  SPOT_RATE_LIMIT: 1200,
  FUTURES_RATE_LIMIT: 1200,
  
  // WebSocket configurations
  WS_PING_INTERVAL: 3 * 60 * 1000, // 3 minutes
  WS_RECONNECT_DELAY: 5000,        // 5 seconds
  WS_CONNECTION_TIMEOUT: 10000,     // 10 seconds
  WS_MAX_RECONNECT_ATTEMPTS: 5,
  
  // HTTP configurations
  HTTP_TIMEOUT: 10000,
  HTTP_MAX_RETRIES: 3,
  HTTP_RETRY_DELAY: 1000,
  
  ERRORS: {
    RATE_LIMIT_EXCEEDED: 'Rate limit exceeded',
    INVALID_SYMBOL: 'Invalid trading pair symbol',
    WS_CONNECTION_ERROR: 'WebSocket connection error',
    WS_SUBSCRIPTION_ERROR: 'WebSocket subscription error'
  }
} as const;

export type Config = typeof config;
```

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

```json
{
  "name": "binance-mcp-server",
  "version": "0.1.0",
  "description": "Binance market data provider with WebSocket support",
  "private": true,
  "type": "module",
  "bin": {
    "binance-mcp-server": "./build/index.js"
  },
  "files": [
    "build"
  ],
  "scripts": {
    "build": "rimraf build && tsc -p tsconfig.json",
    "postbuild": "chmod +x build/index.js",
    "prepare": "npm run build",
    "watch": "tsc --watch -p tsconfig.json",
    "inspector": "npx @modelcontextprotocol/inspector build/index.js",
    "start": "node build/index.js",
    "test": "jest --config=jest.config.js",
    "test:watch": "jest --watch --config=jest.config.js",
    "test:coverage": "jest --coverage --config=jest.config.js",
    "type-check": "tsc --noEmit -p tsconfig.test.json",
    "lint": "eslint 'src/**/*.{js,ts}'"
  },
  "dependencies": {
    "@modelcontextprotocol/sdk": "0.6.0",
    "@types/ws": "^8.5.10",
    "axios": "^1.6.7",
    "ws": "^8.16.0",
    "winston": "^3.11.0"
  },
  "devDependencies": {
    "@jest/globals": "^29.7.0",
    "@types/jest": "^29.5.12",
    "@types/node": "^20.11.24",
    "@typescript-eslint/eslint-plugin": "^7.1.0",
    "@typescript-eslint/parser": "^7.1.0",
    "eslint": "^8.57.0",
    "jest": "^29.7.0",
    "rimraf": "^5.0.5",
    "ts-jest": "^29.1.2",
    "typescript": "^5.3.3"
  }
}
```

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

```typescript
export interface MarketDataParams {
  symbol: string;
  type: 'spot' | 'futures';
}

export interface KlineParams {
  symbol: string;
  type: 'spot' | 'futures';
  interval: string;
  limit?: number;
}

export interface StreamParams {
  symbol: string;
  type: 'spot' | 'futures';
  streams: string[];
}

export interface FuturesDataParams {
  symbol: string;
}

export class APIError extends Error {
  constructor(message: string, public readonly cause?: Error) {
    super(message);
    this.name = 'APIError';
  }
}

// Type guards
export function isMarketDataParams(params: any): params is MarketDataParams {
  return (
    typeof params === 'object' &&
    typeof params.symbol === 'string' &&
    (params.type === 'spot' || params.type === 'futures')
  );
}

export function isKlineParams(params: any): params is KlineParams {
  return (
    typeof params === 'object' &&
    typeof params.symbol === 'string' &&
    (params.type === 'spot' || params.type === 'futures') &&
    typeof params.interval === 'string' &&
    (params.limit === undefined || typeof params.limit === 'number')
  );
}

export function isStreamParams(params: any): params is StreamParams {
  return (
    typeof params === 'object' &&
    typeof params.symbol === 'string' &&
    (params.type === 'spot' || params.type === 'futures') &&
    Array.isArray(params.streams)
  );
}

export function isFuturesDataParams(params: any): params is FuturesDataParams {
  return (
    typeof params === 'object' &&
    typeof params.symbol === 'string'
  );
}
```

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

```bash
#!/bin/bash

# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
NC='\033[0m' # No Color

# Check if we're in the correct directory (has package.json)
if [ ! -f "package.json" ]; then
    echo -e "${RED}Error: package.json not found. Are you in the correct directory?${NC}"
    exit 1
fi

# Function to create directory if it doesn't exist
create_dir() {
    if [ ! -d "$1" ]; then
        mkdir -p "$1"
        echo -e "${GREEN}Created directory: $1${NC}"
    else
        echo "Directory already exists: $1"
    fi
}

# Function to create file if it doesn't exist
create_file() {
    if [ ! -f "$1" ]; then
        touch "$1"
        # Add basic export statement to TypeScript files
        if [[ $1 == *.ts ]]; then
            echo "// $1" > "$1"
            echo "export {}" >> "$1"
        fi
        echo -e "${GREEN}Created file: $1${NC}"
    else
        echo "File already exists: $1"
    fi
}

# Create directory structure
create_dir "src/connectors"
create_dir "src/types"
create_dir "src/utils"

# Create files
create_file "src/connectors/binance-rest.ts"
create_file "src/connectors/binance-ws.ts"
create_file "src/types/market-data.ts"
create_file "src/types/api-types.ts"
create_file "src/utils/logger.ts"
create_file "src/config.ts"

echo -e "\n${GREEN}Directory structure created successfully!${NC}"
echo -e "Next steps:"
echo "1. Implement the TypeScript interfaces in types/"
echo "2. Set up the configuration in config.ts"
echo "3. Implement the connectors in connectors/"
echo "4. Set up logging in utils/logger.ts"
echo "5. Update main server implementation in index.ts"

# Print final directory tree
echo -e "\nFinal directory structure:"
tree
```

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

```typescript
export type StreamEventType = 
  | 'trade'
  | 'ticker'
  | 'bookTicker'
  | 'kline'
  | 'depth'
  | 'forceOrder'    // Liquidation orders
  | 'markPrice'     // Mark price and funding rate
  | 'openInterest'; // Open interest updates

export interface WebSocketMessage<T> {
  stream: string;
  data: T;
  timestamp: number;
}

export type StreamEventData =
  | TradeData
  | TickerData
  | BookTickerData
  | KlineData
  | DepthData
  | ForceOrderData
  | MarkPriceData
  | OpenInterestData;

// Existing interfaces
export interface TradeData {
  e: 'trade';
  E: number;
  s: string;
  t: number;
  p: string;
  q: string;
  b: number;
  a: number;
  T: number;
  m: boolean;
}

export interface TickerData {
  e: '24hrTicker';
  E: number;
  s: string;
  p: string;
  P: string;
  w: string;
  c: string;
  Q: string;
  o: string;
  h: string;
  l: string;
  v: string;
  q: string;
}

export interface BookTickerData {
  e: 'bookTicker';
  u: number;
  s: string;
  b: string;
  B: string;
  a: string;
  A: string;
}

export interface KlineData {
  e: 'kline';
  E: number;
  s: string;
  k: {
    t: number;
    T: number;
    s: string;
    i: string;
    f: number;
    L: number;
    o: string;
    c: string;
    h: string;
    l: string;
    v: string;
    n: number;
    x: boolean;
    q: string;
    V: string;
    Q: string;
  };
}

export interface DepthData {
  e: 'depthUpdate';
  E: number;
  s: string;
  U: number;
  u: number;
  b: [string, string][];
  a: [string, string][];
}

// New Futures-specific interfaces
export interface ForceOrderData {
  e: 'forceOrder';
  E: number;              // Event time
  o: {
    s: string;           // Symbol
    S: 'SELL' | 'BUY';   // Side
    o: 'LIMIT';          // Order type
    f: 'IOC';           // Time in force
    q: string;          // Original quantity
    p: string;          // Price
    ap: string;         // Average price
    X: 'FILLED';        // Order status
    l: string;          // Last filled quantity
    z: string;          // Cumulative filled quantity
    T: number;          // Trade time
  };
}

export interface MarkPriceData {
  e: 'markPriceUpdate';
  E: number;           // Event time
  s: string;          // Symbol
  p: string;          // Mark price
  i: string;          // Index price
  P: string;          // Estimated settle price
  r: string;          // Funding rate
  T: number;          // Next funding time
}

export interface OpenInterestData {
  e: 'openInterest';
  E: number;          // Event time
  s: string;          // Symbol
  o: string;          // Open interest
  T: number;          // Transaction time
}
```

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

```typescript
import { BinanceWebSocketManager } from '../connectors/binance-ws';
import WebSocket from 'ws';
import { StreamEventData, TradeData } from '../types/ws-stream';

jest.mock('ws');

type MockWebSocket = jest.Mocked<WebSocket> & {
  readyState: number;
};

describe('BinanceWebSocketManager', () => {
  let wsManager: BinanceWebSocketManager;
  let mockWs: MockWebSocket;

  beforeEach(() => {
    wsManager = new BinanceWebSocketManager();
    mockWs = {
      readyState: WebSocket.CONNECTING,
      on: jest.fn(),
      once: jest.fn(),
      emit: jest.fn(),
      close: jest.fn(),
      ping: jest.fn(),
      send: jest.fn(),
      terminate: jest.fn(),
      removeListener: jest.fn(),
      removeAllListeners: jest.fn(),
      setMaxListeners: jest.fn(),
      getMaxListeners: jest.fn(),
      listeners: jest.fn(),
      rawListeners: jest.fn(),
      listenerCount: jest.fn(),
      eventNames: jest.fn(),
      addListener: jest.fn(),
      off: jest.fn(),
      prependListener: jest.fn(),
      prependOnceListener: jest.fn(),
    } as unknown as MockWebSocket;

    (WebSocket as unknown as jest.Mock).mockImplementation(() => mockWs);
  });

  afterEach(() => {
    wsManager.close();
    jest.clearAllMocks();
  });

  it('should successfully subscribe to stream', () => {
    const symbol = 'BTCUSDT';
    const streams = ['trade', 'ticker'] as const;

    wsManager.subscribe(symbol, 'spot', streams);

    expect(WebSocket).toHaveBeenCalledWith(
      expect.stringContaining(`btcusdt@trade/btcusdt@ticker`)
    );
  });

  it('should handle incoming messages correctly', (done) => {
    const symbol = 'BTCUSDT';
    const mockData = {
      stream: 'btcusdt@trade',
      data: {
        e: 'trade',
        E: 123456789,
        s: 'BTCUSDT',
        p: '50000.00',
        q: '1.0'
      } as TradeData
    };

    wsManager.subscribe(symbol, 'spot', ['trade']);
    wsManager.onStreamData(symbol, 'trade', (data: StreamEventData) => {
      expect(data).toEqual(mockData.data);
      done();
    });

    // Simulate receiving a message
    mockWs.emit('message', JSON.stringify(mockData));
  });

  it('should handle reconnection on connection close', () => {
    const symbol = 'BTCUSDT';
    wsManager.subscribe(symbol, 'spot', ['trade']);

    // Simulate connection close
    mockWs.emit('close');

    // Verify that a new connection attempt is made
    expect(WebSocket).toHaveBeenCalledTimes(2);
  });

  it('should clean up resources on unsubscribe', () => {
    const symbol = 'BTCUSDT';
    wsManager.subscribe(symbol, 'spot', ['trade']);
    wsManager.unsubscribe(symbol);

    expect(mockWs.close).toHaveBeenCalled();
  });

  it('should handle multiple stream subscriptions', () => {
    const symbol = 'BTCUSDT';
    const streams = ['trade', 'ticker', 'bookTicker'] as const;

    wsManager.subscribe(symbol, 'spot', streams);

    expect(WebSocket).toHaveBeenCalledWith(
      expect.stringContaining('btcusdt@trade/btcusdt@ticker/btcusdt@bookTicker')
    );
  });

  it('should properly maintain connection state', () => {
    const symbol = 'BTCUSDT';
    wsManager.subscribe(symbol, 'spot', ['trade']);

    // Update mockWs.readyState which is now properly typed
    mockWs.readyState = WebSocket.OPEN;
    mockWs.emit('open');

    expect(wsManager.getConnectionState(symbol)).toBe(WebSocket.OPEN);
  });
});
```

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

```typescript
import WebSocket from 'ws';
import { config } from '../config.js';
import { logger } from '../utils/logger.js';
import {
  WebSocketMessage,
  StreamEventType,
  StreamEventData,
  TradeData,
  TickerData,
  BookTickerData,
  KlineData,
  ForceOrderData,
  MarkPriceData,
  OpenInterestData
} from '../types/ws-stream.js';

type WSReadyState = number;

interface StreamSubscription {
  symbol: string;
  type: 'spot' | 'futures';
  streams: StreamEventType[];
  reconnectAttempts: number;
  reconnectTimeout?: NodeJS.Timeout;
}

type MessageHandler = (data: StreamEventData) => void;

export class BinanceWebSocketManager {
  private connections: Map<string, WebSocket>;
  private pingIntervals: Map<string, NodeJS.Timeout>;
  private messageCallbacks: Map<string, Map<StreamEventType, MessageHandler[]>>;
  private subscriptions: Map<string, StreamSubscription>;
  private readonly MAX_RECONNECT_ATTEMPTS = 5;
  private readonly RECONNECT_DELAY = config.WS_RECONNECT_DELAY || 5000;

  constructor() {
    this.connections = new Map();
    this.pingIntervals = new Map();
    this.messageCallbacks = new Map();
    this.subscriptions = new Map();
  }

  public subscribe(symbol: string, type: 'spot' | 'futures', streams: StreamEventType[]): void {
    const subscription: StreamSubscription = {
      symbol,
      type,
      streams,
      reconnectAttempts: 0
    };
    
    if (!this.messageCallbacks.has(symbol)) {
      this.messageCallbacks.set(symbol, new Map());
    }
    
    const symbolCallbacks = this.messageCallbacks.get(symbol)!;
    streams.forEach(stream => {
      if (!symbolCallbacks.has(stream)) {
        symbolCallbacks.set(stream, []);
      }
    });

    this.subscriptions.set(symbol, subscription);
    this.connectWebSocket(subscription);
  }

  private connectWebSocket(subscription: StreamSubscription): void {
    const { symbol, type, streams } = subscription;
    const wsUrl = type === 'spot' ? config.SPOT_WS_URL : config.FUTURES_WS_URL;
    
    // Handle special futures streams
    const streamNames = streams.map(stream => {
      if (type === 'futures') {
        switch (stream) {
          case 'forceOrder':
            return `${symbol.toLowerCase()}@forceOrder`;
          case 'markPrice':
            return `${symbol.toLowerCase()}@markPrice@1s`; // 1s update frequency
          case 'openInterest':
            return `${symbol.toLowerCase()}@openInterest@1s`;
          default:
            return `${symbol.toLowerCase()}@${stream}`;
        }
      }
      return `${symbol.toLowerCase()}@${stream}`;
    });
    
    try {
      const ws = new WebSocket(`${wsUrl}/${streamNames.join('/')}`);
      
      ws.on('open', () => {
        logger.info(`WebSocket connected for ${symbol} ${streams.join(', ')}`);
        subscription.reconnectAttempts = 0;
        this.setupPingInterval(symbol, ws);
      });

      ws.on('message', (data: WebSocket.Data) => {
        try {
          const message = JSON.parse(data.toString()) as WebSocketMessage<StreamEventData>;
          this.handleStreamMessage(symbol, message);
        } catch (error) {
          logger.error('Error parsing WebSocket message:', error);
        }
      });

      ws.on('error', (error: Error) => {
        logger.error(`WebSocket error for ${symbol}:`, error);
      });

      ws.on('close', () => {
        logger.info(`WebSocket closed for ${symbol}`);
        this.cleanup(symbol);
        this.handleReconnection(subscription);
      });

      ws.on('pong', () => {
        logger.debug(`Received pong from ${symbol} WebSocket`);
      });

      this.connections.set(symbol, ws);
    } catch (error) {
      logger.error(`Error creating WebSocket connection for ${symbol}:`, error);
      this.handleReconnection(subscription);
      throw error;
    }
  }

  private handleStreamMessage(symbol: string, message: WebSocketMessage<StreamEventData>): void {
    const symbolCallbacks = this.messageCallbacks.get(symbol);
    if (!symbolCallbacks) return;

    // Extract stream type from the stream name
    const streamParts = message.stream.split('@');
    if (streamParts.length < 2) return;

    let streamType = streamParts[1] as StreamEventType;
    // Handle special cases where the stream name has additional parts (e.g., markPrice@1s)
    if (streamParts.length > 2) {
      streamType = streamParts[1].split('@')[0] as StreamEventType;
    }

    const handlers = symbolCallbacks.get(streamType);
    
    if (handlers) {
      handlers.forEach(handler => {
        try {
          handler(message.data);
        } catch (error) {
          logger.error(`Error in message handler for ${symbol} ${streamType}:`, error);
        }
      });
    }
  }

  public onStreamData(symbol: string, streamType: StreamEventType, handler: MessageHandler): void {
    const symbolCallbacks = this.messageCallbacks.get(symbol);
    if (!symbolCallbacks) {
      logger.error(`No callbacks registered for symbol ${symbol}`);
      return;
    }

    const handlers = symbolCallbacks.get(streamType) || [];
    handlers.push(handler);
    symbolCallbacks.set(streamType, handlers);
  }

  private handleReconnection(subscription: StreamSubscription): void {
    const { symbol, reconnectAttempts } = subscription;

    if (reconnectAttempts >= this.MAX_RECONNECT_ATTEMPTS) {
      logger.error(`Max reconnection attempts reached for ${symbol}`);
      return;
    }

    subscription.reconnectAttempts++;
    const delay = this.RECONNECT_DELAY * Math.pow(2, reconnectAttempts - 1); // Exponential backoff

    logger.info(`Attempting to reconnect ${symbol} in ${delay}ms (attempt ${reconnectAttempts})`);

    subscription.reconnectTimeout = setTimeout(() => {
      this.connectWebSocket(subscription);
    }, delay);
  }

  private setupPingInterval(symbol: string, ws: WebSocket): void {
    const interval = setInterval(() => {
      if (ws.readyState === WebSocket.OPEN) {
        ws.ping((error?: Error) => {
          if (error) {
            logger.error(`Error sending ping for ${symbol}:`, error);
          }
        });
      }
    }, config.WS_PING_INTERVAL);
    this.pingIntervals.set(symbol, interval);
  }

  private cleanup(symbol: string): void {
    const interval = this.pingIntervals.get(symbol);
    if (interval) {
      clearInterval(interval);
      this.pingIntervals.delete(symbol);
    }

    const subscription = this.subscriptions.get(symbol);
    if (subscription?.reconnectTimeout) {
      clearTimeout(subscription.reconnectTimeout);
    }

    this.connections.delete(symbol);
  }

  public unsubscribe(symbol: string): void {
    const ws = this.connections.get(symbol);
    if (ws) {
      ws.close();
    }
    this.cleanup(symbol);
    this.subscriptions.delete(symbol);
    this.messageCallbacks.delete(symbol);
  }

  public close(): void {
    this.connections.forEach((ws, symbol) => {
      ws.close();
      this.cleanup(symbol);
    });
    this.subscriptions.clear();
    this.messageCallbacks.clear();
  }

  public getConnectionState(symbol: string): WSReadyState | undefined {
    const ws = this.connections.get(symbol);
    return ws?.readyState;
  }

  public isSubscribed(symbol: string, streamType: StreamEventType): boolean {
    const subscription = this.subscriptions.get(symbol);
    return subscription?.streams.includes(streamType) || false;
  }
}
```

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

```typescript
import axios, { AxiosInstance } from 'axios';
import { config } from '../config.js';
import { logger } from '../utils/logger.js';
import { APIError } from '../types/api-types.js';

export class BinanceRestConnector {
  private readonly axiosInstance: AxiosInstance;
  private readonly retryDelay = config.HTTP_RETRY_DELAY;
  private readonly maxRetries = config.HTTP_MAX_RETRIES;

  constructor() {
    this.axiosInstance = axios.create({
      timeout: config.HTTP_TIMEOUT,
      headers: {
        'Content-Type': 'application/json'
      }
    });
    logger.info('BinanceRestConnector initialized');
    logger.info(`Futures REST URL: ${config.FUTURES_REST_URL}`);
  }

  private async executeWithRetry<T>(operation: () => Promise<T>, retries = 0): Promise<T> {
    try {
      return await operation();
    } catch (error) {
      if (retries >= this.maxRetries) {
        throw error;
      }

      const delay = this.retryDelay * Math.pow(2, retries);
      logger.warn(`Request failed, retrying in ${delay}ms...`);
      
      await new Promise(resolve => setTimeout(resolve, delay));
      return this.executeWithRetry(operation, retries + 1);
    }
  }

  public async getMarketData(symbol: string, type: 'spot' | 'futures'): Promise<any> {
    try {
      logger.info(`Getting ${type} market data for ${symbol}`);

      if (type === 'spot') {
        const data = await this.executeWithRetry(() =>
          this.axiosInstance.get(`${config.SPOT_REST_URL}/ticker/24hr`, {
            params: { symbol: symbol.toUpperCase() }
          }).then(response => response.data)
        );
        logger.info('Successfully fetched spot market data');
        return data;
      }

      // For futures, fetch all relevant data in parallel
      logger.info('Fetching futures data from multiple endpoints...');
      
      try {
        const [
          marketData,
          openInterest,
          fundingData,
          liquidations
        ] = await Promise.all([
          // Basic market data
          this.executeWithRetry(() =>
            this.axiosInstance.get(`${config.FUTURES_REST_URL}/ticker/24hr`, {
              params: { symbol: symbol.toUpperCase() }
            }).then(response => {
              logger.info('Successfully fetched futures ticker data');
              return response.data;
            })
          ),
          // Open interest
          this.executeWithRetry(() =>
            this.axiosInstance.get(`${config.FUTURES_REST_URL}/openInterest`, {
              params: { symbol: symbol.toUpperCase() }
            }).then(response => {
              logger.info('Successfully fetched open interest data');
              return response.data;
            })
          ),
          // Premium index (funding rate)
          this.executeWithRetry(() =>
            this.axiosInstance.get(`${config.FUTURES_REST_URL}/premiumIndex`, {
              params: {
                symbol: symbol.toUpperCase()
              }
            }).then(response => {
              logger.info('Successfully fetched funding rate data');
              return response.data;
            })
          ),
          // Recent liquidations
          this.executeWithRetry(() =>
            this.axiosInstance.get(`${config.FUTURES_REST_URL}/forceOrders`, {
              params: {
                symbol: symbol.toUpperCase(),
                startTime: Date.now() - 24 * 60 * 60 * 1000,
                limit: 100
              }
            }).then(response => {
              logger.info('Successfully fetched liquidations data');
              return response.data;
            })
          )
        ]);

        logger.info('Successfully fetched all futures data, combining responses...');

        // Combine all futures data with correct field mappings
        const combinedData = {
          ...marketData,
          openInterest: openInterest.openInterest,
          fundingRate: fundingData.lastFundingRate,
          markPrice: fundingData.markPrice,
          nextFundingTime: fundingData.nextFundingTime,
          liquidations24h: liquidations.length,
          liquidationVolume24h: liquidations.reduce((sum: number, order: any) => 
            sum + parseFloat(order.executedQty), 0
          )
        };

        logger.info('Successfully combined futures data');
        return combinedData;

      } catch (error) {
        logger.error('Error in futures data Promise.all:', error);
        throw error;
      }

    } catch (error) {
      logger.error('Error fetching market data:', error);
      throw new APIError('Failed to fetch market data', error as Error);
    }
  }

  public async getFuturesOpenInterest(symbol: string): Promise<any> {
    try {
      logger.info(`Getting futures open interest for ${symbol}`);
      const response = await this.executeWithRetry(() =>
        this.axiosInstance.get(`${config.FUTURES_REST_URL}/openInterest`, {
          params: { symbol: symbol.toUpperCase() }
        })
      );
      logger.info('Successfully fetched open interest data');
      return response.data;
    } catch (error) {
      logger.error('Error fetching open interest:', error);
      throw new APIError('Failed to fetch open interest data', error as Error);
    }
  }

  public async getFuturesFundingRate(symbol: string): Promise<any> {
    try {
      logger.info(`Getting futures funding rate for ${symbol}`);
      const response = await this.executeWithRetry(() =>
        this.axiosInstance.get(`${config.FUTURES_REST_URL}/premiumIndex`, {
          params: {
            symbol: symbol.toUpperCase()
          }
        })
      );
      logger.info('Successfully fetched funding rate data');
      return response.data;
    } catch (error) {
      logger.error('Error fetching funding rate:', error);
      throw new APIError('Failed to fetch funding rate data', error as Error);
    }
  }

  public async getFuturesLiquidations(symbol: string): Promise<any> {
    try {
      logger.info(`Getting futures liquidations for ${symbol}`);
      const response = await this.executeWithRetry(() =>
        this.axiosInstance.get(`${config.FUTURES_REST_URL}/forceOrders`, {
          params: {
            symbol: symbol.toUpperCase(),
            startTime: Date.now() - 24 * 60 * 60 * 1000,
            limit: 1000
          }
        })
      );
      logger.info('Successfully fetched liquidations data');
      return response.data;
    } catch (error) {
      logger.error('Error fetching liquidations:', error);
      throw new APIError('Failed to fetch liquidations data', error as Error);
    }
  }

  public async getKlines(
    symbol: string,
    type: 'spot' | 'futures',
    interval: string,
    limit?: number
  ): Promise<any> {
    try {
      logger.info(`Getting ${type} klines for ${symbol}`);
      const baseUrl = type === 'spot' ? config.SPOT_REST_URL : config.FUTURES_REST_URL;
      const response = await this.executeWithRetry(() =>
        this.axiosInstance.get(`${baseUrl}/klines`, {
          params: {
            symbol: symbol.toUpperCase(),
            interval,
            limit: limit || 500
          }
        })
      );
      logger.info('Successfully fetched klines data');
      return response.data;
    } catch (error) {
      logger.error('Error fetching klines:', error);
      throw new APIError('Failed to fetch klines data', error as Error);
    }
  }

  public async getExchangeInfo(type: 'spot' | 'futures'): Promise<any> {
    try {
      logger.info(`Getting ${type} exchange info`);
      const baseUrl = type === 'spot' ? config.SPOT_REST_URL : config.FUTURES_REST_URL;
      const response = await this.executeWithRetry(() =>
        this.axiosInstance.get(`${baseUrl}/exchangeInfo`)
      );
      logger.info('Successfully fetched exchange info');
      return response.data;
    } catch (error) {
      logger.error('Error fetching exchange info:', error);
      throw new APIError('Failed to fetch exchange info', error as Error);
    }
  }
}
```

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

```typescript
#!/usr/bin/env node

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";

import { BinanceWebSocketManager } from './connectors/binance-ws.js';
import { BinanceRestConnector } from './connectors/binance-rest.js';
import { config } from './config.js';
import { logger } from './utils/logger.js';
import { 
  MarketDataParams, 
  KlineParams, 
  StreamParams, 
  FuturesDataParams,
  APIError,
  isMarketDataParams,
  isKlineParams,
  isStreamParams,
  isFuturesDataParams
} from './types/api-types.js';
import { StreamEventData } from './types/ws-stream.js';

const wsManager = new BinanceWebSocketManager();
const restConnector = new BinanceRestConnector();

const server = new Server(
  {
    name: config.NAME,
    version: config.VERSION,
    description: 'Binance market data provider with WebSocket support'
  },
  {
    capabilities: {
      tools: {},
    },
  }
);

// List available tools
server.setRequestHandler(ListToolsRequestSchema, async () => {
  return {
    tools: [
      {
        name: "get_market_data",
        description: "Get comprehensive market data for a trading pair",
        inputSchema: {
          type: "object",
          properties: {
            symbol: { 
              type: "string", 
              description: "Trading pair symbol (e.g., BTCUSDT)" 
            },
            type: { 
              type: "string", 
              enum: ["spot", "futures"], 
              description: "Market type" 
            }
          },
          required: ["symbol", "type"]
        }
      },
      {
        name: "test_futures_endpoints",
        description: "Test individual futures endpoints",
        inputSchema: {
          type: "object",
          properties: {
            symbol: { 
              type: "string", 
              description: "Trading pair symbol (e.g., BTCUSDT)" 
            }
          },
          required: ["symbol"]
        }
      },
      {
        name: "get_futures_open_interest",
        description: "Get current open interest for a futures trading pair",
        inputSchema: {
          type: "object",
          properties: {
            symbol: { 
              type: "string", 
              description: "Trading pair symbol (e.g., BTCUSDT)" 
            }
          },
          required: ["symbol"]
        }
      },
      {
        name: "get_futures_funding_rate",
        description: "Get current funding rate for a futures trading pair",
        inputSchema: {
          type: "object",
          properties: {
            symbol: { 
              type: "string", 
              description: "Trading pair symbol (e.g., BTCUSDT)" 
            }
          },
          required: ["symbol"]
        }
      },
      {
        name: "get_klines",
        description: "Get historical candlestick data",
        inputSchema: {
          type: "object",
          properties: {
            symbol: { 
              type: "string", 
              description: "Trading pair symbol (e.g., BTCUSDT)" 
            },
            type: { 
              type: "string", 
              enum: ["spot", "futures"], 
              description: "Market type" 
            },
            interval: {
              type: "string",
              enum: ["1m", "5m", "15m", "30m", "1h", "4h", "1d", "1w", "1M"],
              description: "Kline/candlestick chart interval"
            },
            limit: {
              type: "number",
              description: "Number of klines to retrieve (default 500, max 1000)"
            }
          },
          required: ["symbol", "type", "interval"]
        }
      },
      {
        name: "subscribe_market_data",
        description: "Subscribe to real-time market data updates",
        inputSchema: {
          type: "object",
          properties: {
            symbol: { 
              type: "string", 
              description: "Trading pair symbol (e.g., BTCUSDT)" 
            },
            type: { 
              type: "string", 
              enum: ["spot", "futures"], 
              description: "Market type" 
            },
            streams: {
              type: "array",
              items: {
                type: "string",
                enum: ["ticker", "trade", "kline", "depth", "forceOrder", "markPrice", "openInterest"]
              },
              description: "List of data streams to subscribe to"
            }
          },
          required: ["symbol", "type", "streams"]
        }
      }
    ]
  };
});

// Handle tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  try {
    switch (request.params.name) {
      case "get_market_data": {
        if (!isMarketDataParams(request.params.arguments)) {
          throw new Error('Invalid market data parameters');
        }
        const { symbol, type } = request.params.arguments;
        const data = await restConnector.getMarketData(symbol, type);
        return {
          content: [{
            type: "text",
            text: JSON.stringify(data, null, 2)
          }]
        };
      }

      case "test_futures_endpoints": {
        if (!isFuturesDataParams(request.params.arguments)) {
          throw new Error('Invalid futures data parameters');
        }
        const { symbol } = request.params.arguments;
        
        // Test each endpoint individually
        const openInterest = await restConnector.getFuturesOpenInterest(symbol);
        const fundingRate = await restConnector.getFuturesFundingRate(symbol);
        const liquidations = await restConnector.getFuturesLiquidations(symbol);

        // Return all test results
        return {
          content: [{
            type: "text",
            text: JSON.stringify({
              openInterest,
              fundingRate,
              liquidations
            }, null, 2)
          }]
        };
      }

      case "get_futures_open_interest": {
        if (!isFuturesDataParams(request.params.arguments)) {
          throw new Error('Invalid futures data parameters');
        }
        const { symbol } = request.params.arguments;
        const data = await restConnector.getFuturesOpenInterest(symbol);
        return {
          content: [{
            type: "text",
            text: JSON.stringify(data, null, 2)
          }]
        };
      }

      case "get_futures_funding_rate": {
        if (!isFuturesDataParams(request.params.arguments)) {
          throw new Error('Invalid futures data parameters');
        }
        const { symbol } = request.params.arguments;
        const data = await restConnector.getFuturesFundingRate(symbol);
        return {
          content: [{
            type: "text",
            text: JSON.stringify(data, null, 2)
          }]
        };
      }

      case "get_klines": {
        if (!isKlineParams(request.params.arguments)) {
          throw new Error('Invalid kline parameters');
        }
        const { symbol, type, interval, limit } = request.params.arguments;
        const data = await restConnector.getKlines(symbol, type, interval, limit);
        return {
          content: [{
            type: "text",
            text: JSON.stringify(data, null, 2)
          }]
        };
      }

      case "subscribe_market_data": {
        if (!isStreamParams(request.params.arguments)) {
          throw new Error('Invalid stream parameters');
        }
        const { symbol, type, streams } = request.params.arguments;
        wsManager.subscribe(symbol, type, streams);
        
        // Set up message handler
        wsManager.onStreamData(symbol, streams[0], (data: StreamEventData) => {
          // Handle real-time data updates
          logger.info(`Received WebSocket data for ${symbol}:`, data);
        });

        return {
          content: [{
            type: "text",
            text: `Successfully subscribed to ${streams.join(", ")} for ${symbol}`
          }]
        };
      }

      default:
        throw new Error(`Unknown tool: ${request.params.name}`);
    }
  } catch (error) {
    const apiError = error as APIError;
    logger.error('Error handling tool request:', apiError);
    throw apiError;
  }
});

// Start the server
async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  logger.info('Binance MCP server started successfully');
}

// Handle cleanup on shutdown
process.on('SIGINT', () => {
  logger.info('Shutting down server...');
  wsManager.close();
  process.exit(0);
});

main().catch((error) => {
  logger.error('Failed to start server:', error);
  process.exit(1);
});
```