#!/usr/bin/env node
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from "zod";
import axios from "axios";
import { SocksProxyAgent } from "socks-proxy-agent";
const BASE_URL = "https://api.binance.com";
// 支持多种代理类型
const httpProxyURL = process.env.HTTP_PROXY || process.env.HTTPS_PROXY;
const socksProxyURL = process.env.SOCKS_PROXY || process.env.SOCKS5_PROXY;
function registerTools(server: McpServer) {
// Order Book
server.tool(
"get_order_book",
{
symbol: z.string().describe("Trading pair symbol, e.g. BTCUSDT"),
limit: z.number().optional().describe("Order book depth, default 100, max 5000")
},
async (args: { symbol: string; limit?: number }) => {
try {
const response = await axios.get(`${BASE_URL}/api/v3/depth`, {
params: {
symbol: args.symbol,
limit: args.limit
},
...getProxyConfig(),
});
return {
content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }]
};
} catch (error: any) {
return {
content: [{ type: "text", text: `Failed to get order book: ${error.message}` }],
isError: true
};
}
}
);
// Recent Trades List
server.tool(
"get_recent_trades",
{
symbol: z.string().describe("Trading pair symbol, e.g. BTCUSDT"),
limit: z.number().optional().describe("Number of trades to return, default 500, max 1000")
},
async (args: { symbol: string; limit?: number }) => {
try {
const response = await axios.get(`${BASE_URL}/api/v3/trades`, {
params: {
symbol: args.symbol,
limit: args.limit
},
...getProxyConfig(),
});
return {
content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }]
};
} catch (error: any) {
return {
content: [{ type: "text", text: `Failed to get recent trades: ${error.message}` }],
isError: true
};
}
}
);
// Historical Trades Lookup
server.tool(
"get_historical_trades",
{
symbol: z.string().describe("Trading pair symbol, e.g. BTCUSDT"),
limit: z.number().optional().describe("Number of trades to return, default 500, max 1000"),
fromId: z.number().optional().describe("Trade ID to start from, default returns the most recent trades")
},
async (args: { symbol: string; limit?: number; fromId?: number }) => {
try {
const response = await axios.get(`${BASE_URL}/api/v3/historicalTrades`, {
params: {
symbol: args.symbol,
limit: args.limit,
fromId: args.fromId
},
headers: {
"X-MBX-APIKEY": process.env.BINANCE_API_KEY || ""
},
...getProxyConfig(),
});
return {
content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }]
};
} catch (error: any) {
return {
content: [{ type: "text", text: `Failed to get historical trades: ${error.message}` }],
isError: true
};
}
}
);
// Aggregate Trades List
server.tool(
"get_aggregate_trades",
{
symbol: z.string().describe("Trading pair symbol, e.g. BTCUSDT"),
fromId: z.number().optional().describe("Aggregate trade ID to start from"),
startTime: z.number().optional().describe("Start timestamp (milliseconds)"),
endTime: z.number().optional().describe("End timestamp (milliseconds)"),
limit: z.number().optional().describe("Number of trades to return, default 500, max 1000")
},
async (args: { symbol: string; fromId?: number; startTime?: number; endTime?: number; limit?: number }) => {
try {
const response = await axios.get(`${BASE_URL}/api/v3/aggTrades`, {
params: {
symbol: args.symbol,
fromId: args.fromId,
startTime: args.startTime,
endTime: args.endTime,
limit: args.limit
},
...getProxyConfig(),
});
return {
content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }]
};
} catch (error: any) {
return {
content: [{ type: "text", text: `Failed to get aggregate trades: ${error.message}` }],
isError: true
};
}
}
);
// K-line/Candlestick Data
server.tool(
"get_klines",
{
symbol: z.string().describe("Trading pair symbol, e.g. BTCUSDT"),
interval: z.string().describe("K-line interval, e.g. 1m, 3m, 5m, 15m, 30m, 1h, 2h, 4h, 6h, 8h, 12h, 1d, 3d, 1w, 1M"),
startTime: z.number().optional().describe("Start timestamp (milliseconds)"),
endTime: z.number().optional().describe("End timestamp (milliseconds)"),
timeZone: z.string().optional().describe("Time zone, default UTC"),
limit: z.number().optional().describe("Number of K-lines to return, default 500, max 1000")
},
async (args: { symbol: string; interval: string; startTime?: number; endTime?: number; timeZone?: string; limit?: number }) => {
try {
const response = await axios.get(`${BASE_URL}/api/v3/klines`, {
params: {
symbol: args.symbol,
interval: args.interval,
startTime: args.startTime,
endTime: args.endTime,
timeZone: args.timeZone,
limit: args.limit
},
...getProxyConfig(),
});
return {
content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }]
};
} catch (error: any) {
return {
content: [{ type: "text", text: `Failed to get K-line data: ${error.message}` }],
isError: true
};
}
}
);
// UI K-line Data
server.tool(
"get_ui_klines",
{
symbol: z.string().describe("Trading pair symbol, e.g. BTCUSDT"),
interval: z.string().describe("K-line interval, e.g. 1m, 3m, 5m, 15m, 30m, 1h, 2h, 4h, 6h, 8h, 12h, 1d, 3d, 1w, 1M"),
startTime: z.number().optional().describe("Start timestamp (milliseconds)"),
endTime: z.number().optional().describe("End timestamp (milliseconds)"),
timeZone: z.string().optional().describe("Time zone, default UTC"),
limit: z.number().optional().describe("Number of K-lines to return, default 500, max 1000")
},
async (args: { symbol: string; interval: string; startTime?: number; endTime?: number; timeZone?: string; limit?: number }) => {
try {
const response = await axios.get(`${BASE_URL}/api/v3/uiKlines`, {
params: {
symbol: args.symbol,
interval: args.interval,
startTime: args.startTime,
endTime: args.endTime,
timeZone: args.timeZone,
limit: args.limit
},
...getProxyConfig(),
});
return {
content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }]
};
} catch (error: any) {
return {
content: [{ type: "text", text: `Failed to get UI K-line data: ${error.message}` }],
isError: true
};
}
}
);
// Current Average Price
server.tool(
"get_avg_price",
{
symbol: z.string().describe("Trading pair symbol, e.g. BTCUSDT")
},
async (args: { symbol: string }) => {
try {
const response = await axios.get(`${BASE_URL}/api/v3/avgPrice`, {
params: {
symbol: args.symbol
},
...getProxyConfig(),
});
return {
content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }]
};
} catch (error: any) {
return {
content: [{ type: "text", text: `Failed to get average price: ${error.message}` }],
isError: true
};
}
}
);
// 24hr Price Change Statistics
server.tool(
"get_24hr_ticker",
{
symbol: z.string().optional().describe("Trading pair symbol, e.g. BTCUSDT"),
symbols: z.array(z.string()).optional().describe("Array of multiple trading pair symbols")
},
async (args: { symbol?: string; symbols?: string[] }) => {
try {
let params = {};
if (args.symbol) {
params = { symbol: args.symbol };
} else if (args.symbols) {
params = { symbols: JSON.stringify(args.symbols) };
}
const response = await axios.get(`${BASE_URL}/api/v3/ticker/24hr`, {
params,
...getProxyConfig(),
});
return {
content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }]
};
} catch (error: any) {
return {
content: [{ type: "text", text: `Failed to get 24hr price statistics: ${error.message}` }],
isError: true
};
}
}
);
// Trading Day Ticker
server.tool(
"get_trading_day_ticker",
{
symbol: z.string().optional().describe("Trading pair symbol, e.g. BTCUSDT"),
symbols: z.array(z.string()).optional().describe("Array of multiple trading pair symbols"),
timeZone: z.number().optional().describe("Time zone, default 0"),
type: z.enum(["FULL", "MINI"]).optional().describe("Return data type, FULL or MINI")
},
async (args: { symbol?: string; symbols?: string[]; timeZone?: number; type?: "FULL" | "MINI" }) => {
try {
let params: any = {};
if (args.symbol) {
params.symbol = args.symbol;
} else if (args.symbols) {
params.symbols = JSON.stringify(args.symbols);
}
if (args.timeZone) params.timeZone = args.timeZone;
if (args.type) params.type = args.type;
const response = await axios.get(`${BASE_URL}/api/v3/ticker/tradingDay`, {
params,
...getProxyConfig(),
});
return {
content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }]
};
} catch (error: any) {
return {
content: [{ type: "text", text: `Failed to get trading day ticker: ${error.message}` }],
isError: true
};
}
}
);
// Price Ticker
server.tool(
"get_price",
{
symbol: z.string().optional().describe("Trading pair symbol, e.g. BTCUSDT"),
symbols: z.array(z.string()).optional().describe("Array of multiple trading pair symbols")
},
async (args: { symbol?: string; symbols?: string[] }) => {
try {
let params = {};
if (args.symbol) {
params = { symbol: args.symbol };
} else if (args.symbols) {
params = { symbols: JSON.stringify(args.symbols) };
}
const response = await axios.get(`${BASE_URL}/api/v3/ticker/price`, {
params,
...getProxyConfig(),
});
return {
content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }]
};
} catch (error: any) {
return {
content: [{ type: "text", text: `Failed to get price ticker: ${error.message}` }],
isError: true
};
}
}
);
// Order Book Ticker
server.tool(
"get_book_ticker",
{
symbol: z.string().optional().describe("Trading pair symbol, e.g. BTCUSDT"),
symbols: z.array(z.string()).optional().describe("Array of multiple trading pair symbols")
},
async (args: { symbol?: string; symbols?: string[] }) => {
try {
let params = {};
if (args.symbol) {
params = { symbol: args.symbol };
} else if (args.symbols) {
params = { symbols: JSON.stringify(args.symbols) };
}
const response = await axios.get(`${BASE_URL}/api/v3/ticker/bookTicker`, {
params,
...getProxyConfig(),
});
return {
content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }]
};
} catch (error: any) {
return {
content: [{ type: "text", text: `Failed to get order book ticker: ${error.message}` }],
isError: true
};
}
}
);
// Rolling Window Price Change Statistics
server.tool(
"get_rolling_window_ticker",
{
symbol: z.string().optional().describe("Trading pair symbol, e.g. BTCUSDT"),
symbols: z.array(z.string()).optional().describe("Array of multiple trading pair symbols"),
windowSize: z.string().optional().describe("Window size, e.g. 1m, 4h, 1d"),
type: z.enum(["FULL", "MINI"]).optional().describe("Return data type, FULL or MINI")
},
async (args: { symbol?: string; symbols?: string[]; windowSize?: string; type?: "FULL" | "MINI" }) => {
try {
let params: any = {};
if (args.symbol) {
params.symbol = args.symbol;
} else if (args.symbols) {
params.symbols = JSON.stringify(args.symbols);
}
if (args.windowSize) params.windowSize = args.windowSize;
if (args.type) params.type = args.type;
const response = await axios.get(`${BASE_URL}/api/v3/ticker`, {
params,
...getProxyConfig(),
});
return {
content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }]
};
} catch (error: any) {
return {
content: [{ type: "text", text: `Failed to get rolling window price statistics: ${error.message}` }],
isError: true
};
}
}
);
}
function getProxyConfig(): any {
// 优先使用SOCKS5代理
if (socksProxyURL) {
try {
const agent = new SocksProxyAgent(socksProxyURL);
return { httpsAgent: agent, httpAgent: agent };
} catch (error) {
console.error(`Failed to create SOCKS proxy agent: ${error}`);
}
}
// 回退到HTTP代理
if (httpProxyURL) {
try {
const urlInfo = new URL(httpProxyURL);
return {
proxy: {
host: urlInfo.hostname,
port: parseInt(urlInfo.port) || (urlInfo.protocol === 'https:' ? 443 : 80),
protocol: urlInfo.protocol.replace(":", "")
}
};
} catch (error) {
console.error(`Failed to parse HTTP proxy URL: ${error}`);
}
}
return {};
}
async function main() {
const server = new McpServer({
name: "binance-mcp",
version: "1.0.0",
description: "Binance Cryptocurrency Market Data MCP Service"
});
registerTools(server);
let transport = new StdioServerTransport();
await server.connect(transport);
const cleanup = async () => {
// MCP service closed (no console output for proper MCP protocol)
await transport.close();
process.exit(0);
};
process.on("SIGINT", cleanup);
process.on("SIGTERM", cleanup);
// MCP service started (no console output for proper MCP protocol)
}
function handleError(error: Error) {
// Error occurred (no console output for proper MCP protocol)
process.exit(1);
}
main().catch(handleError);