@zebpay_rajesh/zebpay-mcp-server 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of @zebpay_rajesh/zebpay-mcp-server might be problematic. Click here for more details.
- package/.env.example +14 -0
- package/README.md +223 -0
- package/dist/__tests__/errors.test.d.ts +5 -0
- package/dist/__tests__/errors.test.js +147 -0
- package/dist/__tests__/errors.test.js.map +1 -0
- package/dist/__tests__/prompts.test.d.ts +1 -0
- package/dist/__tests__/prompts.test.js +73 -0
- package/dist/__tests__/prompts.test.js.map +1 -0
- package/dist/__tests__/resources.test.d.ts +1 -0
- package/dist/__tests__/resources.test.js +79 -0
- package/dist/__tests__/resources.test.js.map +1 -0
- package/dist/__tests__/validation.test.d.ts +15 -0
- package/dist/__tests__/validation.test.js +64 -0
- package/dist/__tests__/validation.test.js.map +1 -0
- package/dist/config.d.ts +19 -0
- package/dist/config.js +81 -0
- package/dist/config.js.map +1 -0
- package/dist/http/httpClient.d.ts +40 -0
- package/dist/http/httpClient.js +341 -0
- package/dist/http/httpClient.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +60 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/errors.d.ts +21 -0
- package/dist/mcp/errors.js +214 -0
- package/dist/mcp/errors.js.map +1 -0
- package/dist/mcp/logging.d.ts +21 -0
- package/dist/mcp/logging.js +241 -0
- package/dist/mcp/logging.js.map +1 -0
- package/dist/mcp/prompts.d.ts +9 -0
- package/dist/mcp/prompts.js +165 -0
- package/dist/mcp/prompts.js.map +1 -0
- package/dist/mcp/resources.d.ts +9 -0
- package/dist/mcp/resources.js +125 -0
- package/dist/mcp/resources.js.map +1 -0
- package/dist/mcp/tools_futures.d.ts +5 -0
- package/dist/mcp/tools_futures.js +694 -0
- package/dist/mcp/tools_futures.js.map +1 -0
- package/dist/mcp/tools_spot.d.ts +11 -0
- package/dist/mcp/tools_spot.js +2225 -0
- package/dist/mcp/tools_spot.js.map +1 -0
- package/dist/private/FuturesClient.d.ts +57 -0
- package/dist/private/FuturesClient.js +181 -0
- package/dist/private/FuturesClient.js.map +1 -0
- package/dist/private/SpotClient.d.ts +44 -0
- package/dist/private/SpotClient.js +201 -0
- package/dist/private/SpotClient.js.map +1 -0
- package/dist/private/ZebpayAPI.d.ts +19 -0
- package/dist/private/ZebpayAPI.js +172 -0
- package/dist/private/ZebpayAPI.js.map +1 -0
- package/dist/public/PublicClient.d.ts +79 -0
- package/dist/public/PublicClient.js +283 -0
- package/dist/public/PublicClient.js.map +1 -0
- package/dist/public/PublicFuturesClient.d.ts +27 -0
- package/dist/public/PublicFuturesClient.js +187 -0
- package/dist/public/PublicFuturesClient.js.map +1 -0
- package/dist/security/credentials.d.ts +42 -0
- package/dist/security/credentials.js +80 -0
- package/dist/security/credentials.js.map +1 -0
- package/dist/security/signing.d.ts +33 -0
- package/dist/security/signing.js +56 -0
- package/dist/security/signing.js.map +1 -0
- package/dist/types/responses.d.ts +130 -0
- package/dist/types/responses.js +6 -0
- package/dist/types/responses.js.map +1 -0
- package/dist/utils/cache.d.ts +29 -0
- package/dist/utils/cache.js +72 -0
- package/dist/utils/cache.js.map +1 -0
- package/dist/utils/fileLogger.d.ts +10 -0
- package/dist/utils/fileLogger.js +81 -0
- package/dist/utils/fileLogger.js.map +1 -0
- package/dist/utils/metrics.d.ts +35 -0
- package/dist/utils/metrics.js +94 -0
- package/dist/utils/metrics.js.map +1 -0
- package/dist/utils/responseFormatter.d.ts +93 -0
- package/dist/utils/responseFormatter.js +268 -0
- package/dist/utils/responseFormatter.js.map +1 -0
- package/dist/validation/schemas.d.ts +70 -0
- package/dist/validation/schemas.js +48 -0
- package/dist/validation/schemas.js.map +1 -0
- package/dist/validation/validators.d.ts +28 -0
- package/dist/validation/validators.js +129 -0
- package/dist/validation/validators.js.map +1 -0
- package/docs/LOGGING.md +371 -0
- package/docs/zebpay-ai-trading-beginner.png +0 -0
- package/mcp-config.json.example +20 -0
- package/package.json +54 -0
- package/scripts/README.md +103 -0
- package/scripts/clear-logs.js +52 -0
- package/scripts/log-stats.js +264 -0
- package/scripts/log-viewer.js +288 -0
- package/server.json +31 -0
- package/src/__tests__/errors.test.ts +180 -0
- package/src/__tests__/prompts.test.ts +89 -0
- package/src/__tests__/resources.test.ts +95 -0
- package/src/__tests__/validation.test.ts +88 -0
- package/src/config.ts +108 -0
- package/src/http/httpClient.ts +398 -0
- package/src/index.ts +71 -0
- package/src/mcp/errors.ts +262 -0
- package/src/mcp/logging.ts +284 -0
- package/src/mcp/prompts.ts +206 -0
- package/src/mcp/resources.ts +163 -0
- package/src/mcp/tools_futures.ts +874 -0
- package/src/mcp/tools_spot.ts +2702 -0
- package/src/private/FuturesClient.ts +189 -0
- package/src/private/SpotClient.ts +250 -0
- package/src/private/ZebpayAPI.ts +205 -0
- package/src/public/PublicClient.ts +381 -0
- package/src/public/PublicFuturesClient.ts +228 -0
- package/src/security/credentials.ts +114 -0
- package/src/security/signing.ts +98 -0
- package/src/types/responses.ts +146 -0
- package/src/utils/cache.ts +90 -0
- package/src/utils/fileLogger.ts +88 -0
- package/src/utils/metrics.ts +135 -0
- package/src/utils/responseFormatter.ts +361 -0
- package/src/validation/schemas.ts +66 -0
- package/src/validation/validators.ts +189 -0
- package/tsconfig.json +21 -0
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Public client for market data and public endpoints (no authentication required).
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { HttpClient, HttpError } from "../http/httpClient.js";
|
|
6
|
+
import { AppConfig } from "../config.js";
|
|
7
|
+
import { tickerCache, orderBookCache, exchangeInfoCache, currenciesCache } from "../utils/cache.js";
|
|
8
|
+
import { convertHttpErrorToMcpError } from "../mcp/errors.js";
|
|
9
|
+
import { McpError } from "@modelcontextprotocol/sdk/types.js";
|
|
10
|
+
|
|
11
|
+
export interface TickerParams {
|
|
12
|
+
symbol: string; // Required symbol for single ticker
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface OrderBookParams {
|
|
16
|
+
symbol: string; // Required symbol for order book
|
|
17
|
+
limit?: number; // Optional limit for depth (default usually 100)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface OrderBookTickerParams {
|
|
21
|
+
symbol: string; // Required symbol for order book ticker
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface TradesParams {
|
|
25
|
+
symbol: string; // Required symbol for trades
|
|
26
|
+
limit?: number; // Optional limit for number of trades
|
|
27
|
+
page?: number; // Optional page number for pagination
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface KlineParams {
|
|
31
|
+
symbol: string; // Required symbol
|
|
32
|
+
interval: string; // e.g., "1m", "5m", "1h", "1d"
|
|
33
|
+
startTime: number; // Required start timestamp
|
|
34
|
+
endTime: number; // Required end timestamp
|
|
35
|
+
limit?: number; // Optional limit
|
|
36
|
+
category?: string; // Optional category (e.g., "spot")
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface MarketTickerParams {
|
|
40
|
+
symbol: string; // Required symbol for market ticker
|
|
41
|
+
group?: string; // Optional group parameter (e.g., "singapore")
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface MarketBookParams {
|
|
45
|
+
symbol: string; // Required symbol for market book
|
|
46
|
+
converted?: number; // Optional converted parameter (0 or 1)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export class PublicClient {
|
|
50
|
+
constructor(
|
|
51
|
+
private readonly config: AppConfig,
|
|
52
|
+
private readonly http: HttpClient
|
|
53
|
+
) {}
|
|
54
|
+
|
|
55
|
+
private buildUrl(path: string, queryParams?: Record<string, string>): string {
|
|
56
|
+
const trimmed = path.startsWith("/") ? path : `/${path}`;
|
|
57
|
+
let url = `${this.config.spotBaseUrl}${trimmed}`;
|
|
58
|
+
|
|
59
|
+
if (queryParams && Object.keys(queryParams).length > 0) {
|
|
60
|
+
const queryString = Object.entries(queryParams)
|
|
61
|
+
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
|
|
62
|
+
.join("&");
|
|
63
|
+
url += `?${queryString}`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return url;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
private buildMarketUrl(path: string, queryParams?: Record<string, string>): string {
|
|
70
|
+
// Market API uses configurable base URL from config
|
|
71
|
+
const trimmed = path.startsWith("/") ? path : `/${path}`;
|
|
72
|
+
let url = `${this.config.marketBaseUrl}${trimmed}`;
|
|
73
|
+
|
|
74
|
+
if (queryParams && Object.keys(queryParams).length > 0) {
|
|
75
|
+
const queryString = Object.entries(queryParams)
|
|
76
|
+
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
|
|
77
|
+
.join("&");
|
|
78
|
+
url += `?${queryString}`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return url;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Helper method to handle HTTP requests with consistent error handling
|
|
86
|
+
*/
|
|
87
|
+
private async handleRequest<T = unknown>(
|
|
88
|
+
requestFn: () => Promise<{ data: T }>
|
|
89
|
+
): Promise<T> {
|
|
90
|
+
try {
|
|
91
|
+
const response = await requestFn();
|
|
92
|
+
return response.data;
|
|
93
|
+
} catch (err) {
|
|
94
|
+
if (err instanceof HttpError) {
|
|
95
|
+
// Extract error message from various possible response formats
|
|
96
|
+
let errorMessage = err.message || "Unknown API error";
|
|
97
|
+
|
|
98
|
+
if (err.details) {
|
|
99
|
+
if (typeof err.details === "string") {
|
|
100
|
+
errorMessage = err.details;
|
|
101
|
+
} else if (typeof err.details === "object") {
|
|
102
|
+
// Check common error response fields (in order of preference)
|
|
103
|
+
const details = err.details as Record<string, unknown>;
|
|
104
|
+
errorMessage =
|
|
105
|
+
(typeof details.statusDescription === "string" && details.statusDescription) ||
|
|
106
|
+
(typeof details.error === "string" && details.error) ||
|
|
107
|
+
(typeof details.message === "string" && details.message) ||
|
|
108
|
+
(typeof details.msg === "string" && details.msg) ||
|
|
109
|
+
(typeof details.errorMessage === "string" && details.errorMessage) ||
|
|
110
|
+
errorMessage;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
throw convertHttpErrorToMcpError(err.status, errorMessage, err.details);
|
|
115
|
+
}
|
|
116
|
+
// Re-throw MCP errors as-is
|
|
117
|
+
if (err instanceof McpError) {
|
|
118
|
+
throw err;
|
|
119
|
+
}
|
|
120
|
+
// Re-throw other errors
|
|
121
|
+
throw err;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Get all tickers from Zebpay API.
|
|
127
|
+
* Endpoint: GET /api/v2/market/allTickers
|
|
128
|
+
*/
|
|
129
|
+
async getAllTickers(): Promise<unknown> {
|
|
130
|
+
const cacheKey = "allTickers";
|
|
131
|
+
const cached = tickerCache.get(cacheKey);
|
|
132
|
+
if (cached) {
|
|
133
|
+
return cached;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const path = "/market/allTickers";
|
|
137
|
+
const url = this.buildUrl(path);
|
|
138
|
+
const data = await this.handleRequest(() =>
|
|
139
|
+
this.http.request({
|
|
140
|
+
method: "GET",
|
|
141
|
+
url,
|
|
142
|
+
timeoutMs: this.config.timeoutMs,
|
|
143
|
+
retryCount: this.config.retryCount,
|
|
144
|
+
})
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
tickerCache.set(cacheKey, data);
|
|
148
|
+
return data;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Get ticker for a specific symbol from Zebpay API.
|
|
153
|
+
* Endpoint: GET /api/v2/market/ticker?symbol={symbol}
|
|
154
|
+
*/
|
|
155
|
+
async getTicker(params: TickerParams): Promise<unknown> {
|
|
156
|
+
const cacheKey = `ticker:${params.symbol}`;
|
|
157
|
+
const cached = tickerCache.get(cacheKey);
|
|
158
|
+
if (cached) {
|
|
159
|
+
return cached;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const path = "/market/ticker";
|
|
163
|
+
const queryParams: Record<string, string> = {
|
|
164
|
+
symbol: params.symbol,
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const url = this.buildUrl(path, queryParams);
|
|
168
|
+
const data = await this.handleRequest(() =>
|
|
169
|
+
this.http.request({
|
|
170
|
+
method: "GET",
|
|
171
|
+
url,
|
|
172
|
+
timeoutMs: this.config.timeoutMs,
|
|
173
|
+
retryCount: this.config.retryCount,
|
|
174
|
+
})
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
tickerCache.set(cacheKey, data);
|
|
178
|
+
return data;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async getOrderBook(params: OrderBookParams): Promise<unknown> {
|
|
182
|
+
const cacheKey = `orderbook:${params.symbol}:${params.limit || "default"}`;
|
|
183
|
+
const cached = orderBookCache.get(cacheKey);
|
|
184
|
+
if (cached) {
|
|
185
|
+
return cached;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const path = "/market/orderbook";
|
|
189
|
+
const queryParams: Record<string, string> = {
|
|
190
|
+
symbol: params.symbol,
|
|
191
|
+
};
|
|
192
|
+
if (params.limit !== undefined) {
|
|
193
|
+
queryParams.limit = params.limit.toString();
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const url = this.buildUrl(path, queryParams);
|
|
197
|
+
const data = await this.handleRequest(() =>
|
|
198
|
+
this.http.request({
|
|
199
|
+
method: "GET",
|
|
200
|
+
url,
|
|
201
|
+
timeoutMs: this.config.timeoutMs,
|
|
202
|
+
retryCount: this.config.retryCount,
|
|
203
|
+
})
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
orderBookCache.set(cacheKey, data);
|
|
207
|
+
return data;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Get order book ticker for a specific symbol from Zebpay API.
|
|
212
|
+
* Endpoint: GET /api/v2/market/orderbook/ticker?symbol={symbol}
|
|
213
|
+
*/
|
|
214
|
+
async getOrderBookTicker(params: OrderBookTickerParams): Promise<unknown> {
|
|
215
|
+
const path = "/market/orderbook/ticker";
|
|
216
|
+
const queryParams: Record<string, string> = {
|
|
217
|
+
symbol: params.symbol,
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
const url = this.buildUrl(path, queryParams);
|
|
221
|
+
return this.handleRequest(() =>
|
|
222
|
+
this.http.request({
|
|
223
|
+
method: "GET",
|
|
224
|
+
url,
|
|
225
|
+
timeoutMs: this.config.timeoutMs,
|
|
226
|
+
retryCount: this.config.retryCount,
|
|
227
|
+
})
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async getTrades(params: TradesParams): Promise<unknown> {
|
|
232
|
+
const path = "/market/trades";
|
|
233
|
+
const queryParams: Record<string, string> = {
|
|
234
|
+
symbol: params.symbol,
|
|
235
|
+
};
|
|
236
|
+
if (params.limit !== undefined) {
|
|
237
|
+
queryParams.limit = params.limit.toString();
|
|
238
|
+
}
|
|
239
|
+
if (params.page !== undefined) {
|
|
240
|
+
queryParams.page = params.page.toString();
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const url = this.buildUrl(path, queryParams);
|
|
244
|
+
return this.handleRequest(() =>
|
|
245
|
+
this.http.request({
|
|
246
|
+
method: "GET",
|
|
247
|
+
url,
|
|
248
|
+
timeoutMs: this.config.timeoutMs,
|
|
249
|
+
retryCount: this.config.retryCount,
|
|
250
|
+
})
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async getKlines(params: KlineParams): Promise<unknown> {
|
|
255
|
+
const path = "/market/klines";
|
|
256
|
+
const queryParams: Record<string, string> = {
|
|
257
|
+
symbol: params.symbol,
|
|
258
|
+
interval: params.interval,
|
|
259
|
+
startTime: params.startTime.toString(),
|
|
260
|
+
endTime: params.endTime.toString(),
|
|
261
|
+
};
|
|
262
|
+
if (params.limit !== undefined) {
|
|
263
|
+
queryParams.limit = params.limit.toString();
|
|
264
|
+
}
|
|
265
|
+
if (params.category !== undefined) {
|
|
266
|
+
queryParams.category = params.category;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const url = this.buildUrl(path, queryParams);
|
|
270
|
+
return this.handleRequest(() =>
|
|
271
|
+
this.http.request({
|
|
272
|
+
method: "GET",
|
|
273
|
+
url,
|
|
274
|
+
timeoutMs: this.config.timeoutMs,
|
|
275
|
+
retryCount: this.config.retryCount,
|
|
276
|
+
})
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
async getExchangeInfo(): Promise<unknown> {
|
|
281
|
+
const cacheKey = "exchangeInfo";
|
|
282
|
+
const cached = exchangeInfoCache.get(cacheKey);
|
|
283
|
+
if (cached) {
|
|
284
|
+
return cached;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const path = "/ex/exchangeInfo";
|
|
288
|
+
const url = this.buildUrl(path);
|
|
289
|
+
const data = await this.handleRequest(() =>
|
|
290
|
+
this.http.request({
|
|
291
|
+
method: "GET",
|
|
292
|
+
url,
|
|
293
|
+
timeoutMs: this.config.timeoutMs,
|
|
294
|
+
retryCount: this.config.retryCount,
|
|
295
|
+
})
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
exchangeInfoCache.set(cacheKey, data);
|
|
299
|
+
return data;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
async getCurrencies(): Promise<unknown> {
|
|
303
|
+
const cacheKey = "currencies";
|
|
304
|
+
const cached = currenciesCache.get(cacheKey);
|
|
305
|
+
if (cached) {
|
|
306
|
+
return cached;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const path = "/ex/currencies";
|
|
310
|
+
const url = this.buildUrl(path);
|
|
311
|
+
const data = await this.handleRequest(() =>
|
|
312
|
+
this.http.request({
|
|
313
|
+
method: "GET",
|
|
314
|
+
url,
|
|
315
|
+
timeoutMs: this.config.timeoutMs,
|
|
316
|
+
retryCount: this.config.retryCount,
|
|
317
|
+
})
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
currenciesCache.set(cacheKey, data);
|
|
321
|
+
return data;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Get market information from Zebpay market API.
|
|
326
|
+
* Endpoint: GET /api/v1/market
|
|
327
|
+
*/
|
|
328
|
+
async getMarket(): Promise<unknown> {
|
|
329
|
+
const path = "";
|
|
330
|
+
const url = this.buildMarketUrl(path);
|
|
331
|
+
return this.handleRequest(() =>
|
|
332
|
+
this.http.request({
|
|
333
|
+
method: "GET",
|
|
334
|
+
url,
|
|
335
|
+
timeoutMs: this.config.timeoutMs,
|
|
336
|
+
retryCount: this.config.retryCount,
|
|
337
|
+
})
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Get ticker for a specific symbol from Zebpay market API.
|
|
343
|
+
* Endpoint: GET /api/v1/market/{symbol}/ticker
|
|
344
|
+
*/
|
|
345
|
+
async getMarketTicker(params: MarketTickerParams): Promise<unknown> {
|
|
346
|
+
const path = `/${params.symbol}/ticker`;
|
|
347
|
+
const queryParams: Record<string, string> | undefined = params.group
|
|
348
|
+
? { group: params.group }
|
|
349
|
+
: undefined;
|
|
350
|
+
const url = this.buildMarketUrl(path, queryParams);
|
|
351
|
+
return this.handleRequest(() =>
|
|
352
|
+
this.http.request({
|
|
353
|
+
method: "GET",
|
|
354
|
+
url,
|
|
355
|
+
timeoutMs: this.config.timeoutMs,
|
|
356
|
+
retryCount: this.config.retryCount,
|
|
357
|
+
})
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Get order book for a specific symbol from Zebpay market API.
|
|
363
|
+
* Endpoint: GET /api/v1/market/{symbol}/book
|
|
364
|
+
*/
|
|
365
|
+
async getMarketBook(params: MarketBookParams): Promise<unknown> {
|
|
366
|
+
const path = `/${params.symbol}/book`;
|
|
367
|
+
const queryParams: Record<string, string> | undefined = params.converted !== undefined
|
|
368
|
+
? { converted: params.converted.toString() }
|
|
369
|
+
: undefined;
|
|
370
|
+
const url = this.buildMarketUrl(path, queryParams);
|
|
371
|
+
return this.handleRequest(() =>
|
|
372
|
+
this.http.request({
|
|
373
|
+
method: "GET",
|
|
374
|
+
url,
|
|
375
|
+
timeoutMs: this.config.timeoutMs,
|
|
376
|
+
retryCount: this.config.retryCount,
|
|
377
|
+
})
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Public futures client for market data (no authentication required).
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { HttpClient, HttpError } from "../http/httpClient.js";
|
|
6
|
+
import { AppConfig } from "../config.js";
|
|
7
|
+
import { tickerCache, orderBookCache, exchangeInfoCache } from "../utils/cache.js";
|
|
8
|
+
import { convertHttpErrorToMcpError } from "../mcp/errors.js";
|
|
9
|
+
import { McpError } from "@modelcontextprotocol/sdk/types.js";
|
|
10
|
+
|
|
11
|
+
export interface FuturesSymbolParams {
|
|
12
|
+
symbol: string; // e.g., "BTCUSDT"
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface FuturesOrderBookParams {
|
|
16
|
+
symbol: string; // e.g., "BTCUSDT"
|
|
17
|
+
limit?: number; // optional depth, if supported by API in future
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class PublicFuturesClient {
|
|
21
|
+
constructor(
|
|
22
|
+
private readonly config: AppConfig,
|
|
23
|
+
private readonly http: HttpClient
|
|
24
|
+
) {}
|
|
25
|
+
|
|
26
|
+
private buildUrl(path: string, queryParams?: Record<string, string>): string {
|
|
27
|
+
const trimmed = path.startsWith("/") ? path : `/${path}`;
|
|
28
|
+
let url = `${this.config.futuresBaseUrl}${trimmed}`;
|
|
29
|
+
|
|
30
|
+
if (queryParams && Object.keys(queryParams).length > 0) {
|
|
31
|
+
const queryString = Object.entries(queryParams)
|
|
32
|
+
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
|
|
33
|
+
.join("&");
|
|
34
|
+
url += `?${queryString}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return url;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private buildRootUrl(path: string): string {
|
|
41
|
+
// Some endpoints (like healthCheckStatus) live at the root host, not under /api/v1
|
|
42
|
+
const base = this.config.futuresBaseUrl.replace(/\/api\/v1\/?$/, "");
|
|
43
|
+
const trimmed = path.startsWith("/") ? path : `/${path}`;
|
|
44
|
+
return `${base}${trimmed}`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
private async handleRequest<T = unknown>(
|
|
48
|
+
requestFn: () => Promise<{ data: T }>
|
|
49
|
+
): Promise<T> {
|
|
50
|
+
try {
|
|
51
|
+
const response = await requestFn();
|
|
52
|
+
return response.data;
|
|
53
|
+
} catch (err) {
|
|
54
|
+
if (err instanceof HttpError) {
|
|
55
|
+
let errorMessage = err.message || "Unknown API error";
|
|
56
|
+
if (err.details) {
|
|
57
|
+
if (typeof err.details === "string") {
|
|
58
|
+
errorMessage = err.details;
|
|
59
|
+
} else if (typeof err.details === "object") {
|
|
60
|
+
const details = err.details as Record<string, unknown>;
|
|
61
|
+
errorMessage =
|
|
62
|
+
(typeof details.statusDescription === "string" && details.statusDescription) ||
|
|
63
|
+
(typeof details.error === "string" && details.error) ||
|
|
64
|
+
(typeof details.message === "string" && details.message) ||
|
|
65
|
+
(typeof details.msg === "string" && details.msg) ||
|
|
66
|
+
(typeof details.errorMessage === "string" && details.errorMessage) ||
|
|
67
|
+
errorMessage;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
throw convertHttpErrorToMcpError(err.status, errorMessage, err.details);
|
|
71
|
+
}
|
|
72
|
+
if (err instanceof McpError) {
|
|
73
|
+
throw err;
|
|
74
|
+
}
|
|
75
|
+
throw err;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// GET /healthCheckStatus (root host)
|
|
80
|
+
async getHealthCheckStatus(): Promise<unknown> {
|
|
81
|
+
const url = this.buildRootUrl("/healthCheckStatus");
|
|
82
|
+
return this.handleRequest(() =>
|
|
83
|
+
this.http.request({
|
|
84
|
+
method: "GET",
|
|
85
|
+
url,
|
|
86
|
+
timeoutMs: this.config.timeoutMs,
|
|
87
|
+
retryCount: this.config.retryCount,
|
|
88
|
+
})
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// GET /api/v1/market/markets
|
|
93
|
+
async getMarkets(): Promise<unknown> {
|
|
94
|
+
const cacheKey = "futures:markets";
|
|
95
|
+
const cached = tickerCache.get(cacheKey);
|
|
96
|
+
if (cached) return cached;
|
|
97
|
+
|
|
98
|
+
const url = this.buildUrl("/market/markets");
|
|
99
|
+
const data = await this.handleRequest(() =>
|
|
100
|
+
this.http.request({
|
|
101
|
+
method: "GET",
|
|
102
|
+
url,
|
|
103
|
+
timeoutMs: this.config.timeoutMs,
|
|
104
|
+
retryCount: this.config.retryCount,
|
|
105
|
+
})
|
|
106
|
+
);
|
|
107
|
+
tickerCache.set(cacheKey, data);
|
|
108
|
+
return data;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// GET /api/v1/market/marketInfo
|
|
112
|
+
async getMarketInfo(): Promise<unknown> {
|
|
113
|
+
const cacheKey = "futures:marketInfo";
|
|
114
|
+
const cached = tickerCache.get(cacheKey);
|
|
115
|
+
if (cached) return cached;
|
|
116
|
+
|
|
117
|
+
const url = this.buildUrl("/market/marketInfo");
|
|
118
|
+
const data = await this.handleRequest(() =>
|
|
119
|
+
this.http.request({
|
|
120
|
+
method: "GET",
|
|
121
|
+
url,
|
|
122
|
+
timeoutMs: this.config.timeoutMs,
|
|
123
|
+
retryCount: this.config.retryCount,
|
|
124
|
+
})
|
|
125
|
+
);
|
|
126
|
+
tickerCache.set(cacheKey, data);
|
|
127
|
+
return data;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// GET /api/v1/market/orderBook?symbol=BTCUSDT
|
|
131
|
+
async getOrderBook(params: FuturesOrderBookParams): Promise<unknown> {
|
|
132
|
+
const symbol = params.symbol.trim().toUpperCase();
|
|
133
|
+
const cacheKey = `futures:orderbook:${symbol}:${params.limit ?? "default"}`;
|
|
134
|
+
const cached = orderBookCache.get(cacheKey);
|
|
135
|
+
if (cached) return cached;
|
|
136
|
+
|
|
137
|
+
const query: Record<string, string> = { symbol };
|
|
138
|
+
if (params.limit !== undefined) query.limit = String(params.limit);
|
|
139
|
+
const url = this.buildUrl("/market/orderBook", query);
|
|
140
|
+
const data = await this.handleRequest(() =>
|
|
141
|
+
this.http.request({
|
|
142
|
+
method: "GET",
|
|
143
|
+
url,
|
|
144
|
+
timeoutMs: this.config.timeoutMs,
|
|
145
|
+
retryCount: this.config.retryCount,
|
|
146
|
+
})
|
|
147
|
+
);
|
|
148
|
+
orderBookCache.set(cacheKey, data);
|
|
149
|
+
return data;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// GET /api/v1/market/ticker24Hr?symbol=BTCUSDT
|
|
153
|
+
async getTicker24Hr(params: FuturesSymbolParams): Promise<unknown> {
|
|
154
|
+
const symbol = params.symbol.trim().toUpperCase();
|
|
155
|
+
const cacheKey = `futures:ticker24h:${symbol}`;
|
|
156
|
+
const cached = tickerCache.get(cacheKey);
|
|
157
|
+
if (cached) return cached;
|
|
158
|
+
|
|
159
|
+
const url = this.buildUrl("/market/ticker24Hr", { symbol });
|
|
160
|
+
const data = await this.handleRequest(() =>
|
|
161
|
+
this.http.request({
|
|
162
|
+
method: "GET",
|
|
163
|
+
url,
|
|
164
|
+
timeoutMs: this.config.timeoutMs,
|
|
165
|
+
retryCount: this.config.retryCount,
|
|
166
|
+
})
|
|
167
|
+
);
|
|
168
|
+
tickerCache.set(cacheKey, data);
|
|
169
|
+
return data;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// GET /api/v1/market/aggTrade?symbol=BTCUSDT
|
|
173
|
+
async getAggregateTrades(params: FuturesSymbolParams & { limit?: number }): Promise<unknown> {
|
|
174
|
+
const symbol = params.symbol.trim().toUpperCase();
|
|
175
|
+
const query: Record<string, string> = { symbol };
|
|
176
|
+
if (params.limit !== undefined) query.limit = String(params.limit);
|
|
177
|
+
|
|
178
|
+
const url = this.buildUrl("/market/aggTrade", query);
|
|
179
|
+
return this.handleRequest(() =>
|
|
180
|
+
this.http.request({
|
|
181
|
+
method: "GET",
|
|
182
|
+
url,
|
|
183
|
+
timeoutMs: this.config.timeoutMs,
|
|
184
|
+
retryCount: this.config.retryCount,
|
|
185
|
+
})
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// GET /api/v1/exchange/exchangeInfo
|
|
190
|
+
async getExchangeInfo(): Promise<unknown> {
|
|
191
|
+
const cacheKey = "futures:exchangeInfo";
|
|
192
|
+
const cached = exchangeInfoCache.get(cacheKey);
|
|
193
|
+
if (cached) return cached;
|
|
194
|
+
|
|
195
|
+
const url = this.buildUrl("/exchange/exchangeInfo");
|
|
196
|
+
const data = await this.handleRequest(() =>
|
|
197
|
+
this.http.request({
|
|
198
|
+
method: "GET",
|
|
199
|
+
url,
|
|
200
|
+
timeoutMs: this.config.timeoutMs,
|
|
201
|
+
retryCount: this.config.retryCount,
|
|
202
|
+
})
|
|
203
|
+
);
|
|
204
|
+
exchangeInfoCache.set(cacheKey, data);
|
|
205
|
+
return data;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// GET /api/v1/exchange/pairs
|
|
209
|
+
async getExchangePairs(): Promise<unknown> {
|
|
210
|
+
const cacheKey = "futures:exchangePairs";
|
|
211
|
+
const cached = exchangeInfoCache.get(cacheKey);
|
|
212
|
+
if (cached) return cached;
|
|
213
|
+
|
|
214
|
+
const url = this.buildUrl("/exchange/pairs");
|
|
215
|
+
const data = await this.handleRequest(() =>
|
|
216
|
+
this.http.request({
|
|
217
|
+
method: "GET",
|
|
218
|
+
url,
|
|
219
|
+
timeoutMs: this.config.timeoutMs,
|
|
220
|
+
retryCount: this.config.retryCount,
|
|
221
|
+
})
|
|
222
|
+
);
|
|
223
|
+
exchangeInfoCache.set(cacheKey, data);
|
|
224
|
+
return data;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
|