@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.

Files changed (120) hide show
  1. package/.env.example +14 -0
  2. package/README.md +223 -0
  3. package/dist/__tests__/errors.test.d.ts +5 -0
  4. package/dist/__tests__/errors.test.js +147 -0
  5. package/dist/__tests__/errors.test.js.map +1 -0
  6. package/dist/__tests__/prompts.test.d.ts +1 -0
  7. package/dist/__tests__/prompts.test.js +73 -0
  8. package/dist/__tests__/prompts.test.js.map +1 -0
  9. package/dist/__tests__/resources.test.d.ts +1 -0
  10. package/dist/__tests__/resources.test.js +79 -0
  11. package/dist/__tests__/resources.test.js.map +1 -0
  12. package/dist/__tests__/validation.test.d.ts +15 -0
  13. package/dist/__tests__/validation.test.js +64 -0
  14. package/dist/__tests__/validation.test.js.map +1 -0
  15. package/dist/config.d.ts +19 -0
  16. package/dist/config.js +81 -0
  17. package/dist/config.js.map +1 -0
  18. package/dist/http/httpClient.d.ts +40 -0
  19. package/dist/http/httpClient.js +341 -0
  20. package/dist/http/httpClient.js.map +1 -0
  21. package/dist/index.d.ts +1 -0
  22. package/dist/index.js +60 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/mcp/errors.d.ts +21 -0
  25. package/dist/mcp/errors.js +214 -0
  26. package/dist/mcp/errors.js.map +1 -0
  27. package/dist/mcp/logging.d.ts +21 -0
  28. package/dist/mcp/logging.js +241 -0
  29. package/dist/mcp/logging.js.map +1 -0
  30. package/dist/mcp/prompts.d.ts +9 -0
  31. package/dist/mcp/prompts.js +165 -0
  32. package/dist/mcp/prompts.js.map +1 -0
  33. package/dist/mcp/resources.d.ts +9 -0
  34. package/dist/mcp/resources.js +125 -0
  35. package/dist/mcp/resources.js.map +1 -0
  36. package/dist/mcp/tools_futures.d.ts +5 -0
  37. package/dist/mcp/tools_futures.js +694 -0
  38. package/dist/mcp/tools_futures.js.map +1 -0
  39. package/dist/mcp/tools_spot.d.ts +11 -0
  40. package/dist/mcp/tools_spot.js +2225 -0
  41. package/dist/mcp/tools_spot.js.map +1 -0
  42. package/dist/private/FuturesClient.d.ts +57 -0
  43. package/dist/private/FuturesClient.js +181 -0
  44. package/dist/private/FuturesClient.js.map +1 -0
  45. package/dist/private/SpotClient.d.ts +44 -0
  46. package/dist/private/SpotClient.js +201 -0
  47. package/dist/private/SpotClient.js.map +1 -0
  48. package/dist/private/ZebpayAPI.d.ts +19 -0
  49. package/dist/private/ZebpayAPI.js +172 -0
  50. package/dist/private/ZebpayAPI.js.map +1 -0
  51. package/dist/public/PublicClient.d.ts +79 -0
  52. package/dist/public/PublicClient.js +283 -0
  53. package/dist/public/PublicClient.js.map +1 -0
  54. package/dist/public/PublicFuturesClient.d.ts +27 -0
  55. package/dist/public/PublicFuturesClient.js +187 -0
  56. package/dist/public/PublicFuturesClient.js.map +1 -0
  57. package/dist/security/credentials.d.ts +42 -0
  58. package/dist/security/credentials.js +80 -0
  59. package/dist/security/credentials.js.map +1 -0
  60. package/dist/security/signing.d.ts +33 -0
  61. package/dist/security/signing.js +56 -0
  62. package/dist/security/signing.js.map +1 -0
  63. package/dist/types/responses.d.ts +130 -0
  64. package/dist/types/responses.js +6 -0
  65. package/dist/types/responses.js.map +1 -0
  66. package/dist/utils/cache.d.ts +29 -0
  67. package/dist/utils/cache.js +72 -0
  68. package/dist/utils/cache.js.map +1 -0
  69. package/dist/utils/fileLogger.d.ts +10 -0
  70. package/dist/utils/fileLogger.js +81 -0
  71. package/dist/utils/fileLogger.js.map +1 -0
  72. package/dist/utils/metrics.d.ts +35 -0
  73. package/dist/utils/metrics.js +94 -0
  74. package/dist/utils/metrics.js.map +1 -0
  75. package/dist/utils/responseFormatter.d.ts +93 -0
  76. package/dist/utils/responseFormatter.js +268 -0
  77. package/dist/utils/responseFormatter.js.map +1 -0
  78. package/dist/validation/schemas.d.ts +70 -0
  79. package/dist/validation/schemas.js +48 -0
  80. package/dist/validation/schemas.js.map +1 -0
  81. package/dist/validation/validators.d.ts +28 -0
  82. package/dist/validation/validators.js +129 -0
  83. package/dist/validation/validators.js.map +1 -0
  84. package/docs/LOGGING.md +371 -0
  85. package/docs/zebpay-ai-trading-beginner.png +0 -0
  86. package/mcp-config.json.example +20 -0
  87. package/package.json +54 -0
  88. package/scripts/README.md +103 -0
  89. package/scripts/clear-logs.js +52 -0
  90. package/scripts/log-stats.js +264 -0
  91. package/scripts/log-viewer.js +288 -0
  92. package/server.json +31 -0
  93. package/src/__tests__/errors.test.ts +180 -0
  94. package/src/__tests__/prompts.test.ts +89 -0
  95. package/src/__tests__/resources.test.ts +95 -0
  96. package/src/__tests__/validation.test.ts +88 -0
  97. package/src/config.ts +108 -0
  98. package/src/http/httpClient.ts +398 -0
  99. package/src/index.ts +71 -0
  100. package/src/mcp/errors.ts +262 -0
  101. package/src/mcp/logging.ts +284 -0
  102. package/src/mcp/prompts.ts +206 -0
  103. package/src/mcp/resources.ts +163 -0
  104. package/src/mcp/tools_futures.ts +874 -0
  105. package/src/mcp/tools_spot.ts +2702 -0
  106. package/src/private/FuturesClient.ts +189 -0
  107. package/src/private/SpotClient.ts +250 -0
  108. package/src/private/ZebpayAPI.ts +205 -0
  109. package/src/public/PublicClient.ts +381 -0
  110. package/src/public/PublicFuturesClient.ts +228 -0
  111. package/src/security/credentials.ts +114 -0
  112. package/src/security/signing.ts +98 -0
  113. package/src/types/responses.ts +146 -0
  114. package/src/utils/cache.ts +90 -0
  115. package/src/utils/fileLogger.ts +88 -0
  116. package/src/utils/metrics.ts +135 -0
  117. package/src/utils/responseFormatter.ts +361 -0
  118. package/src/validation/schemas.ts +66 -0
  119. package/src/validation/validators.ts +189 -0
  120. package/tsconfig.json +21 -0
@@ -0,0 +1,189 @@
1
+ /*
2
+ Futures client for basic trading and positions.
3
+ */
4
+
5
+ import { ZebpayAPI } from "./ZebpayAPI.js";
6
+
7
+ export class FuturesClient {
8
+ constructor(private readonly api: ZebpayAPI) {}
9
+
10
+ // GET /api/v1/wallet/balance
11
+ async getWalletBalance(): Promise<unknown> {
12
+ return this.api.request({
13
+ method: "GET",
14
+ path: "/wallet/balance",
15
+ useFutures: true,
16
+ });
17
+ }
18
+
19
+ // POST /api/v1/trade/order
20
+ async placeOrder(params: {
21
+ symbol: string;
22
+ amount: number | string;
23
+ side: "BUY" | "SELL";
24
+ type: "MARKET" | "LIMIT";
25
+ marginAsset: string;
26
+ price?: number | string; // for LIMIT orders
27
+ clientOrderId?: string;
28
+ }): Promise<unknown> {
29
+ const body: Record<string, unknown> = {
30
+ symbol: params.symbol,
31
+ amount: typeof params.amount === "string" ? params.amount : String(params.amount),
32
+ side: params.side,
33
+ type: params.type,
34
+ marginAsset: params.marginAsset,
35
+ };
36
+ if (params.price !== undefined) body.price = typeof params.price === "string" ? params.price : String(params.price);
37
+ if (params.clientOrderId) body.clientOrderId = params.clientOrderId;
38
+ return this.api.request({
39
+ method: "POST",
40
+ path: "/trade/order",
41
+ body,
42
+ useFutures: true,
43
+ });
44
+ }
45
+
46
+ // POST /api/v1/trade/addMargin
47
+ async addMargin(params: { symbol: string; amount: number | string; marginAsset: string }): Promise<unknown> {
48
+ const body = {
49
+ symbol: params.symbol,
50
+ amount: typeof params.amount === "string" ? params.amount : String(params.amount),
51
+ marginAsset: params.marginAsset,
52
+ };
53
+ return this.api.request({
54
+ method: "POST",
55
+ path: "/trade/addMargin",
56
+ body,
57
+ useFutures: true,
58
+ });
59
+ }
60
+
61
+ // POST /api/v1/trade/reduceMargin
62
+ async reduceMargin(params: { symbol: string; amount: number | string; marginAsset: string }): Promise<unknown> {
63
+ const body = {
64
+ symbol: params.symbol,
65
+ amount: typeof params.amount === "string" ? params.amount : String(params.amount),
66
+ marginAsset: params.marginAsset,
67
+ };
68
+ return this.api.request({
69
+ method: "POST",
70
+ path: "/trade/reduceMargin",
71
+ body,
72
+ useFutures: true,
73
+ });
74
+ }
75
+
76
+ // GET /api/v1/trade/order/open-orders
77
+ async getOpenOrders(params: { symbol?: string; limit?: number; since?: string | number }): Promise<unknown> {
78
+ const query: Record<string, string> = {};
79
+ if (params.symbol) query.symbol = params.symbol;
80
+ if (params.limit !== undefined) query.limit = String(params.limit);
81
+ if (params.since !== undefined) query.since = String(params.since);
82
+ return this.api.request({
83
+ method: "GET",
84
+ path: "/trade/order/open-orders",
85
+ queryParams: query,
86
+ useFutures: true,
87
+ });
88
+ }
89
+
90
+ // GET /api/v1/trade/order/history
91
+ async getOrderHistory(params: { page?: number; pageSize?: number }): Promise<unknown> {
92
+ const query: Record<string, string> = {};
93
+ if (params.page !== undefined) query.page = String(params.page);
94
+ if (params.pageSize !== undefined) query.pageSize = String(params.pageSize);
95
+ return this.api.request({
96
+ method: "GET",
97
+ path: "/trade/order/history",
98
+ queryParams: query,
99
+ useFutures: true,
100
+ });
101
+ }
102
+
103
+ // GET /api/v1/trade/linkedOrders/{clientOrderId}
104
+ async getLinkedOrders(clientOrderId: string): Promise<unknown> {
105
+ const path = `/trade/linkedOrders/${encodeURIComponent(clientOrderId)}`;
106
+ return this.api.request({
107
+ method: "GET",
108
+ path,
109
+ useFutures: true,
110
+ });
111
+ }
112
+
113
+ // GET /api/v1/trade/positions
114
+ async getPositions(params: { status?: string } = {}): Promise<unknown> {
115
+ const query: Record<string, string> = {};
116
+ if (params.status) query.status = params.status;
117
+ return this.api.request({
118
+ method: "GET",
119
+ path: "/trade/positions",
120
+ queryParams: query,
121
+ useFutures: true,
122
+ });
123
+ }
124
+
125
+ // GET /api/v1/trade/history
126
+ async getTradeHistory(params: { page?: number; pageSize?: number }): Promise<unknown> {
127
+ const query: Record<string, string> = {};
128
+ if (params.page !== undefined) query.page = String(params.page);
129
+ if (params.pageSize !== undefined) query.pageSize = String(params.pageSize);
130
+ return this.api.request({
131
+ method: "GET",
132
+ path: "/trade/history",
133
+ queryParams: query,
134
+ useFutures: true,
135
+ });
136
+ }
137
+
138
+ // GET /api/v1/trade/transaction/history
139
+ async getTransactionHistory(params: { page?: number; pageSize?: number }): Promise<unknown> {
140
+ const query: Record<string, string> = {};
141
+ if (params.page !== undefined) query.page = String(params.page);
142
+ if (params.pageSize !== undefined) query.pageSize = String(params.pageSize);
143
+ return this.api.request({
144
+ method: "GET",
145
+ path: "/trade/transaction/history",
146
+ queryParams: query,
147
+ useFutures: true,
148
+ });
149
+ }
150
+
151
+ // DELETE /api/v1/trade/order
152
+ async deleteOrder(params: { orderId?: string | number; clientOrderId?: string }): Promise<unknown> {
153
+ const query: Record<string, string> = {};
154
+ if (params.orderId !== undefined) query.orderId = String(params.orderId);
155
+ if (params.clientOrderId !== undefined) query.clientOrderId = params.clientOrderId;
156
+ return this.api.request({
157
+ method: "DELETE",
158
+ path: "/trade/order",
159
+ queryParams: query,
160
+ useFutures: true,
161
+ });
162
+ }
163
+
164
+ // GET /api/v1/trade/userLeverage
165
+ async getUserLeverage(params: { symbol: string }): Promise<unknown> {
166
+ const query: Record<string, string> = { symbol: params.symbol };
167
+ return this.api.request({
168
+ method: "GET",
169
+ path: "/trade/userLeverage",
170
+ queryParams: query,
171
+ useFutures: true,
172
+ });
173
+ }
174
+
175
+ // POST /api/v1/trade/update/userLeverage
176
+ async updateUserLeverage(params: { symbol: string; leverage: number | string }): Promise<unknown> {
177
+ const body = {
178
+ symbol: params.symbol,
179
+ leverage: typeof params.leverage === "string" ? params.leverage : String(params.leverage),
180
+ };
181
+ return this.api.request({
182
+ method: "POST",
183
+ path: "/trade/update/userLeverage",
184
+ body,
185
+ useFutures: true,
186
+ });
187
+ }
188
+ }
189
+
@@ -0,0 +1,250 @@
1
+ /*
2
+ Spot client for market orders.
3
+ */
4
+
5
+ import { ZebpayAPI } from "./ZebpayAPI.js";
6
+
7
+ export interface SpotMarketOrderParams {
8
+ symbol: string; // e.g., BTC-INR
9
+ side: "BUY" | "SELL";
10
+ quoteOrderAmount?: string; // Quote currency value (e.g., INR) - will be mapped to body based on side
11
+ amount?: string; // Base currency amount (e.g., BTC) - will be mapped to body based on side
12
+ clientOrderId?: string; // Must contain only letters, numbers, dots, colons, slashes, underscores, and hyphens, 1-36 characters
13
+ platform?: string;
14
+ }
15
+
16
+ export interface SpotLimitOrderParams {
17
+ symbol: string;
18
+ side: "BUY" | "SELL";
19
+ quoteOrderAmount?: string; // Quote currency value (e.g., INR) - will be mapped to body based on side
20
+ amount?: string; // Base currency amount (e.g., BTC) - will be mapped to body based on side
21
+ price: string; // Limit price as string to preserve precision
22
+ clientOrderId?: string; // Must contain only letters, numbers, dots, colons, slashes, underscores, and hyphens, 1-36 characters
23
+ platform?: string;
24
+ }
25
+
26
+ export interface GetOrdersParams {
27
+ symbol: string; // Trading pair symbol (required)
28
+ status?: "ACTIVE" | "FILLED" | "CANCELLED"; // Order status (default: ACTIVE)
29
+ currentPage?: number; // Current page number (default: 1)
30
+ pageSize?: number; // Number of records per page (default: 20)
31
+ startTime?: number; // Start time in milliseconds
32
+ endTime?: number; // End time in milliseconds
33
+ }
34
+
35
+ export interface GetExchangeFeeParams {
36
+ symbol: string; // Trading pair symbol (required)
37
+ side: "BUY" | "SELL"; // Order side (required)
38
+ }
39
+
40
+ export class SpotClient {
41
+ constructor(private readonly api: ZebpayAPI) {}
42
+
43
+ async placeMarketOrder(params: SpotMarketOrderParams): Promise<unknown> {
44
+ const path = "/ex/orders";
45
+
46
+ // Build body based on order side:
47
+ // BUY orders: use quoteOrderAmount (value in quote currency, e.g., INR)
48
+ // SELL orders: use amount (quantity in base currency, e.g., BTC)
49
+ const body: Record<string, unknown> = {
50
+ symbol: params.symbol,
51
+ side: params.side,
52
+ type: "MARKET",
53
+ };
54
+
55
+ // Map to the appropriate field based on side
56
+ if (params.side === "BUY") {
57
+ // For BUY orders, use quoteOrderAmount in body
58
+ if (params.quoteOrderAmount) {
59
+ body.quoteOrderAmount = params.quoteOrderAmount;
60
+ } else if (params.amount) {
61
+ // If amount is provided for BUY, it should already be converted to quoteOrderAmount by the tool
62
+ body.quoteOrderAmount = params.amount;
63
+ }
64
+ } else {
65
+ // For SELL orders, use amount in body
66
+ if (params.amount) {
67
+ body.amount = params.amount;
68
+ } else if (params.quoteOrderAmount) {
69
+ // If quoteOrderAmount is provided for SELL, it should already be converted to amount by the tool
70
+ body.amount = params.quoteOrderAmount;
71
+ }
72
+ }
73
+
74
+ if (params.clientOrderId) {
75
+ body.clientOrderId = params.clientOrderId;
76
+ }
77
+
78
+ if (params.platform) {
79
+ body.platform = params.platform;
80
+ }
81
+
82
+ return this.api.request({
83
+ method: "POST",
84
+ path,
85
+ body,
86
+ useFutures: false,
87
+ });
88
+ }
89
+
90
+ async placeLimitOrder(params: SpotLimitOrderParams): Promise<unknown> {
91
+ const path = "/ex/orders";
92
+
93
+ // Build body based on order side:
94
+ // BUY orders: use quoteOrderAmount (value in quote currency, e.g., INR)
95
+ // SELL orders: use amount (quantity in base currency, e.g., BTC)
96
+ const body: Record<string, unknown> = {
97
+ symbol: params.symbol,
98
+ side: params.side,
99
+ type: "LIMIT",
100
+ price: params.price,
101
+ };
102
+
103
+ // Map to the appropriate field based on side
104
+ if (params.side === "BUY") {
105
+ // For BUY orders, use quoteOrderAmount in body
106
+ if (params.quoteOrderAmount) {
107
+ body.quoteOrderAmount = params.quoteOrderAmount;
108
+ } else if (params.amount) {
109
+ // If amount is provided for BUY, it should already be converted to quoteOrderAmount by the tool
110
+ body.quoteOrderAmount = params.amount;
111
+ }
112
+ } else {
113
+ // For SELL orders, use amount in body
114
+ if (params.amount) {
115
+ body.amount = params.amount;
116
+ } else if (params.quoteOrderAmount) {
117
+ // If quoteOrderAmount is provided for SELL, it should already be converted to amount by the tool
118
+ body.amount = params.quoteOrderAmount;
119
+ }
120
+ }
121
+
122
+ if (params.clientOrderId) {
123
+ body.clientOrderId = params.clientOrderId;
124
+ }
125
+ if (params.platform) {
126
+ body.platform = params.platform;
127
+ }
128
+ return this.api.request({
129
+ method: "POST",
130
+ path,
131
+ body,
132
+ useFutures: false,
133
+ });
134
+ }
135
+
136
+ async cancelOrdersBySymbol(symbol: string): Promise<unknown> {
137
+ const path = "/ex/orders";
138
+ return this.api.request({
139
+ method: "DELETE",
140
+ path,
141
+ useFutures: false,
142
+ queryParams: {
143
+ symbol: symbol.toUpperCase(),
144
+ },
145
+ });
146
+ }
147
+
148
+ async cancelAllOrders(): Promise<unknown> {
149
+ const path = "/ex/orders/cancelAll";
150
+ return this.api.request({
151
+ method: "DELETE",
152
+ path,
153
+ useFutures: false,
154
+ });
155
+ }
156
+
157
+ async getOrderFills(orderId: string): Promise<unknown> {
158
+ const path = "/ex/order/fills/";
159
+ return this.api.request({
160
+ method: "GET",
161
+ path,
162
+ useFutures: false,
163
+ queryParams: {
164
+ orderId,
165
+ },
166
+ });
167
+ }
168
+
169
+ async getOrderById(orderId: string): Promise<unknown> {
170
+ const path = "/ex/order";
171
+ return this.api.request({
172
+ method: "GET",
173
+ path,
174
+ useFutures: false,
175
+ queryParams: {
176
+ orderId,
177
+ },
178
+ });
179
+ }
180
+
181
+ async cancelOrderById(orderId: string): Promise<unknown> {
182
+ const path = "/ex/order";
183
+ return this.api.request({
184
+ method: "DELETE",
185
+ path,
186
+ useFutures: false,
187
+ queryParams: {
188
+ orderId,
189
+ },
190
+ });
191
+ }
192
+
193
+ async getBalance(currencies?: string): Promise<unknown> {
194
+ const path = "/account/balance";
195
+ const queryParams: Record<string, string> | undefined = currencies
196
+ ? { currencies }
197
+ : undefined;
198
+ return this.api.request({
199
+ method: "GET",
200
+ path,
201
+ useFutures: false,
202
+ queryParams,
203
+ });
204
+ }
205
+
206
+ async getOrders(params: GetOrdersParams): Promise<unknown> {
207
+ const path = "/ex/orders";
208
+ const queryParams: Record<string, string> = {
209
+ symbol: params.symbol.toUpperCase(),
210
+ };
211
+
212
+ if (params.status) {
213
+ queryParams.status = params.status;
214
+ }
215
+ if (params.currentPage !== undefined) {
216
+ queryParams.currentPage = params.currentPage.toString();
217
+ }
218
+ if (params.pageSize !== undefined) {
219
+ queryParams.pageSize = params.pageSize.toString();
220
+ }
221
+ if (params.startTime !== undefined) {
222
+ queryParams.startTime = params.startTime.toString();
223
+ }
224
+ if (params.endTime !== undefined) {
225
+ queryParams.endTime = params.endTime.toString();
226
+ }
227
+
228
+ return this.api.request({
229
+ method: "GET",
230
+ path,
231
+ useFutures: false,
232
+ queryParams,
233
+ });
234
+ }
235
+
236
+ async getExchangeFee(params: GetExchangeFeeParams): Promise<unknown> {
237
+ const path = `/ex/tradefee`;
238
+
239
+ return this.api.request({
240
+ method: "GET",
241
+ path,
242
+ useFutures: false,
243
+ queryParams: {
244
+ symbol: params.symbol.toUpperCase(),
245
+ side: params.side,
246
+ },
247
+ });
248
+ }
249
+ }
250
+
@@ -0,0 +1,205 @@
1
+ /*
2
+ Core ZebpayAPI encapsulates signing, sending, and error normalization.
3
+ */
4
+
5
+ import { AppConfig } from "../config.js";
6
+ import { CredentialsProvider } from "../security/credentials.js";
7
+ import { buildAuthHeaders, buildQueryStringAuthHeaders, signQueryString, signPayloadString, signRequest } from "../security/signing.js";
8
+ import { HttpClient, HttpError } from "../http/httpClient.js";
9
+ import { convertHttpErrorToMcpError, createInternalError } from "../mcp/errors.js";
10
+ import { McpError } from "@modelcontextprotocol/sdk/types.js";
11
+
12
+ export interface RequestOptions {
13
+ method: string;
14
+ path: string; // path starting with '/'
15
+ body?: unknown;
16
+ useFutures?: boolean; // choose base URL
17
+ queryParams?: Record<string, string>; // optional query parameters
18
+ }
19
+
20
+ export class ZebpayAPI {
21
+ constructor(
22
+ private readonly config: AppConfig,
23
+ private readonly creds: CredentialsProvider,
24
+ private readonly http: HttpClient
25
+ ) {}
26
+
27
+ private baseUrl(useFutures: boolean): string {
28
+ return useFutures ? this.config.futuresBaseUrl : this.config.spotBaseUrl;
29
+ }
30
+
31
+ private buildUrl(useFutures: boolean, path: string, queryParams?: Record<string, string>): string {
32
+ const trimmed = path.startsWith("/") ? path : `/${path}`;
33
+ let url = `${this.baseUrl(useFutures)}${trimmed}`;
34
+
35
+ if (queryParams && Object.keys(queryParams).length > 0) {
36
+ const queryString = Object.entries(queryParams)
37
+ .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
38
+ .join("&");
39
+ url += `?${queryString}`;
40
+
41
+ // Debug logging
42
+ if (this.config.logLevel === "debug") {
43
+ console.error(JSON.stringify({
44
+ level: "debug",
45
+ msg: "buildUrl details",
46
+ queryParams,
47
+ queryString,
48
+ finalUrl: url
49
+ }));
50
+ }
51
+ }
52
+
53
+ return url;
54
+ }
55
+
56
+ async request<T = unknown>({ method, path, body, useFutures = false, queryParams: providedQueryParams }: RequestOptions): Promise<T> {
57
+ const apiKey = this.creds.getApiKey();
58
+ const secret = this.creds.getSecretKey();
59
+
60
+ const methodUpper = method.toUpperCase();
61
+ const isGetOrDelete = methodUpper === "GET" || methodUpper === "DELETE";
62
+ const isPost = methodUpper === "POST";
63
+ const includeBody = isPost && body !== undefined && body !== null;
64
+
65
+ let url: string;
66
+ let authHeaders: Record<string, string>;
67
+ let finalBody: unknown = body;
68
+
69
+ if (isGetOrDelete) {
70
+ // For GET and DELETE requests, use query-string based signing (Zebpay API format)
71
+ const timestamp = Date.now().toString();
72
+ const queryParams: Record<string, string> = {
73
+ timestamp,
74
+ ...providedQueryParams, // Merge provided query params with timestamp
75
+ };
76
+
77
+ // Build query string for signing (sorted alphabetically for consistent signing)
78
+ // This same sorted order must be used in the URL to match the signature
79
+ const sortedEntries = Object.entries(queryParams)
80
+ .sort(([a], [b]) => a.localeCompare(b));
81
+ const queryString = sortedEntries
82
+ .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
83
+ .join("&");
84
+
85
+ // Sign the query string
86
+ const signature = signQueryString(queryString, secret);
87
+
88
+ // Build auth headers (only API key and signature, no timestamp header)
89
+ authHeaders = buildQueryStringAuthHeaders(apiKey, signature, this.config.signingHeaders);
90
+
91
+ // Build URL with the same sorted query string to match the signature
92
+ const trimmed = path.startsWith("/") ? path : `/${path}`;
93
+ url = `${this.baseUrl(useFutures)}${trimmed}?${queryString}`;
94
+
95
+ // Debug logging
96
+ if (this.config.logLevel === "debug") {
97
+ console.error(JSON.stringify({
98
+ level: "debug",
99
+ msg: `${methodUpper} request details`,
100
+ path,
101
+ queryString,
102
+ timestamp,
103
+ url,
104
+ hasQueryParams: url.includes("?")
105
+ }));
106
+ }
107
+ } else if (isPost) {
108
+ // For POST requests, sign the JSON body directly (Zebpay API format)
109
+ const timestamp = Date.now();
110
+
111
+ // Prepare payload with timestamp added
112
+ const payload = body ? { ...(body as Record<string, unknown>), timestamp } : { timestamp };
113
+
114
+ // Stringify the payload (deterministic JSON stringify)
115
+ const payloadString = JSON.stringify(payload);
116
+
117
+ // Sign the payload string directly
118
+ const signature = signPayloadString(payloadString, secret);
119
+
120
+ // Build auth headers (only API key and signature, no timestamp header)
121
+ authHeaders = buildQueryStringAuthHeaders(apiKey, signature, this.config.signingHeaders);
122
+
123
+ // Set the final body with timestamp included
124
+ finalBody = payload;
125
+ url = this.buildUrl(useFutures, path);
126
+
127
+ // Debug logging
128
+ if (this.config.logLevel === "debug") {
129
+ console.error(JSON.stringify({
130
+ level: "debug",
131
+ msg: "POST request details",
132
+ path,
133
+ payloadString,
134
+ timestamp,
135
+ signature
136
+ }));
137
+ }
138
+ } else {
139
+ // For other methods (PUT, PATCH, etc.), fall back to original signing method
140
+ const signature = signRequest(method, path, includeBody ? body : undefined, secret, {
141
+ headers: this.config.signingHeaders,
142
+ includeBody,
143
+ });
144
+ authHeaders = buildAuthHeaders(apiKey, signature, this.config.signingHeaders);
145
+ url = this.buildUrl(useFutures, path);
146
+ }
147
+
148
+ try {
149
+ const res = await this.http.request<T>({
150
+ method,
151
+ url,
152
+ headers: authHeaders,
153
+ body: isPost ? finalBody : (isGetOrDelete ? undefined : body),
154
+ timeoutMs: this.config.timeoutMs,
155
+ retryCount: this.config.retryCount,
156
+ });
157
+ return res.data as T;
158
+ } catch (err) {
159
+ if (err instanceof HttpError) {
160
+ // Extract error message from various possible response formats
161
+ let errorMessage = err.message || "Unknown API error";
162
+
163
+ // Extract error message and preserve full details for better error handling
164
+ let errorDetails = err.details;
165
+
166
+ if (err.details) {
167
+ if (typeof err.details === "string") {
168
+ errorMessage = err.details;
169
+ } else if (typeof err.details === "object") {
170
+ // Check common error response fields (in order of preference)
171
+ const details = err.details as Record<string, unknown>;
172
+
173
+ // Extract statusDescription first (most specific)
174
+ if (typeof details.statusDescription === "string" && details.statusDescription) {
175
+ errorMessage = details.statusDescription;
176
+ } else if (typeof details.error === "string" && details.error) {
177
+ errorMessage = details.error;
178
+ } else if (typeof details.message === "string" && details.message) {
179
+ errorMessage = details.message;
180
+ } else if (typeof details.msg === "string" && details.msg) {
181
+ errorMessage = details.msg;
182
+ } else if (typeof details.errorMessage === "string" && details.errorMessage) {
183
+ errorMessage = details.errorMessage;
184
+ }
185
+
186
+ // Preserve the full details object for error conversion
187
+ errorDetails = details;
188
+ }
189
+ }
190
+
191
+ throw convertHttpErrorToMcpError(err.status, errorMessage, errorDetails, err.isHtmlResponse);
192
+ }
193
+ // Re-throw MCP errors as-is
194
+ if (err instanceof McpError) {
195
+ throw err;
196
+ }
197
+ // Wrap other errors as internal errors
198
+ throw createInternalError(
199
+ err instanceof Error ? err.message : String(err),
200
+ { originalError: String(err) }
201
+ );
202
+ }
203
+ }
204
+ }
205
+