@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,288 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Log Viewer - Easy tracking of API requests and responses
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* node scripts/log-viewer.js # View all logs
|
|
7
|
+
* node scripts/log-viewer.js --type=http # Only HTTP logs
|
|
8
|
+
* node scripts/log-viewer.js --type=mcp # Only MCP logs
|
|
9
|
+
* node scripts/log-viewer.js --errors # Only errors
|
|
10
|
+
* node scripts/log-viewer.js --symbol=BTC-INR # Filter by symbol
|
|
11
|
+
* node scripts/log-viewer.js --last=50 # Last 50 entries
|
|
12
|
+
* node scripts/log-viewer.js --watch # Watch mode (tail -f)
|
|
13
|
+
* node scripts/log-viewer.js --tool=placeMarketOrder # Filter by tool
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { readFileSync, existsSync, watchFile } from 'fs';
|
|
17
|
+
import { resolve } from 'path';
|
|
18
|
+
|
|
19
|
+
const args = process.argv.slice(2);
|
|
20
|
+
const options = {
|
|
21
|
+
type: getArg('type'), // http, mcp, http_request, http_response, etc.
|
|
22
|
+
errors: hasFlag('errors'), // Show only errors
|
|
23
|
+
symbol: getArg('symbol'), // Filter by trading symbol
|
|
24
|
+
tool: getArg('tool'), // Filter by tool name
|
|
25
|
+
last: parseInt(getArg('last')) || null, // Show last N entries
|
|
26
|
+
watch: hasFlag('watch'), // Watch mode
|
|
27
|
+
raw: hasFlag('raw'), // Show raw JSON
|
|
28
|
+
correlationId: getArg('correlation'), // Filter by correlation ID
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const logPath = resolve('logs/mcp-server.log');
|
|
32
|
+
|
|
33
|
+
// ANSI color codes
|
|
34
|
+
const colors = {
|
|
35
|
+
reset: '\x1b[0m',
|
|
36
|
+
bright: '\x1b[1m',
|
|
37
|
+
dim: '\x1b[2m',
|
|
38
|
+
red: '\x1b[31m',
|
|
39
|
+
green: '\x1b[32m',
|
|
40
|
+
yellow: '\x1b[33m',
|
|
41
|
+
blue: '\x1b[34m',
|
|
42
|
+
magenta: '\x1b[35m',
|
|
43
|
+
cyan: '\x1b[36m',
|
|
44
|
+
white: '\x1b[37m',
|
|
45
|
+
gray: '\x1b[90m',
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
function getArg(name) {
|
|
49
|
+
const arg = args.find(a => a.startsWith(`--${name}=`));
|
|
50
|
+
return arg ? arg.split('=')[1] : null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function hasFlag(name) {
|
|
54
|
+
return args.includes(`--${name}`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function colorize(text, color) {
|
|
58
|
+
return `${colors[color] || ''}${text}${colors.reset}`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function formatLog(logEntry) {
|
|
62
|
+
if (options.raw) {
|
|
63
|
+
return JSON.stringify(logEntry, null, 2);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const timestamp = new Date(logEntry.timestamp).toLocaleString();
|
|
67
|
+
const level = logEntry.level?.toUpperCase() || 'INFO';
|
|
68
|
+
|
|
69
|
+
// Level colors
|
|
70
|
+
let levelColor = 'white';
|
|
71
|
+
if (level === 'ERROR') levelColor = 'red';
|
|
72
|
+
else if (level === 'WARN') levelColor = 'yellow';
|
|
73
|
+
else if (level === 'INFO') levelColor = 'green';
|
|
74
|
+
else if (level === 'DEBUG') levelColor = 'blue';
|
|
75
|
+
|
|
76
|
+
const levelText = colorize(level.padEnd(5), levelColor);
|
|
77
|
+
const timeText = colorize(timestamp, 'gray');
|
|
78
|
+
|
|
79
|
+
// Format based on log type
|
|
80
|
+
if (logEntry.type === 'http_request') {
|
|
81
|
+
return [
|
|
82
|
+
`${levelText} ${timeText} ${colorize('HTTP REQUEST', 'cyan')}`,
|
|
83
|
+
` ${colorize(logEntry.method, 'bright')} ${logEntry.url}`,
|
|
84
|
+
` ${colorize('Headers:', 'dim')} ${JSON.stringify(logEntry.headers)}`,
|
|
85
|
+
logEntry.body ? ` ${colorize('Body:', 'dim')} ${JSON.stringify(logEntry.body)}` : '',
|
|
86
|
+
].filter(Boolean).join('\n');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (logEntry.type === 'http_response') {
|
|
90
|
+
const statusColor = logEntry.status >= 400 ? 'red' : 'green';
|
|
91
|
+
return [
|
|
92
|
+
`${levelText} ${timeText} ${colorize('HTTP RESPONSE', 'cyan')}`,
|
|
93
|
+
` ${colorize(logEntry.method, 'bright')} ${logEntry.url}`,
|
|
94
|
+
` ${colorize('Status:', 'dim')} ${colorize(logEntry.status, statusColor)} (${logEntry.durationMs}ms)`,
|
|
95
|
+
` ${colorize('Headers:', 'dim')} ${JSON.stringify(logEntry.headers)}`,
|
|
96
|
+
logEntry.body ? ` ${colorize('Body:', 'dim')} ${JSON.stringify(logEntry.body).substring(0, 200)}${JSON.stringify(logEntry.body).length > 200 ? '...' : ''}` : '',
|
|
97
|
+
].filter(Boolean).join('\n');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (logEntry.type === 'http_error') {
|
|
101
|
+
return [
|
|
102
|
+
`${levelText} ${timeText} ${colorize('HTTP ERROR', 'red')}`,
|
|
103
|
+
` ${colorize(logEntry.method, 'bright')} ${logEntry.url}`,
|
|
104
|
+
` ${colorize('Error:', 'red')} ${logEntry.error}`,
|
|
105
|
+
` ${colorize('Duration:', 'dim')} ${logEntry.durationMs}ms`,
|
|
106
|
+
logEntry.stack ? ` ${colorize('Stack:', 'dim')} ${logEntry.stack.split('\n')[0]}` : '',
|
|
107
|
+
].filter(Boolean).join('\n');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (logEntry.type === 'mcp_request') {
|
|
111
|
+
return [
|
|
112
|
+
`${levelText} ${timeText} ${colorize('MCP REQUEST', 'magenta')}`,
|
|
113
|
+
` ${colorize('Tool:', 'bright')} ${logEntry.tool}`,
|
|
114
|
+
` ${colorize('Correlation:', 'dim')} ${logEntry.correlationId}`,
|
|
115
|
+
` ${colorize('Params:', 'dim')} ${JSON.stringify(logEntry.params)}`,
|
|
116
|
+
].filter(Boolean).join('\n');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (logEntry.type === 'mcp_response') {
|
|
120
|
+
return [
|
|
121
|
+
`${levelText} ${timeText} ${colorize('MCP RESPONSE', 'magenta')}`,
|
|
122
|
+
` ${colorize('Tool:', 'bright')} ${logEntry.tool}`,
|
|
123
|
+
` ${colorize('Correlation:', 'dim')} ${logEntry.correlationId}`,
|
|
124
|
+
` ${colorize('Duration:', 'dim')} ${logEntry.durationMs}ms`,
|
|
125
|
+
` ${colorize('Success:', 'green')} ${logEntry.success}`,
|
|
126
|
+
logEntry.result ? ` ${colorize('Result:', 'dim')} ${JSON.stringify(logEntry.result).substring(0, 200)}${JSON.stringify(logEntry.result).length > 200 ? '...' : ''}` : '',
|
|
127
|
+
].filter(Boolean).join('\n');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (logEntry.type === 'mcp_error') {
|
|
131
|
+
return [
|
|
132
|
+
`${levelText} ${timeText} ${colorize('MCP ERROR', 'red')}`,
|
|
133
|
+
` ${colorize('Tool:', 'bright')} ${logEntry.tool}`,
|
|
134
|
+
` ${colorize('Correlation:', 'dim')} ${logEntry.correlationId}`,
|
|
135
|
+
` ${colorize('Error:', 'red')} ${logEntry.error}`,
|
|
136
|
+
logEntry.errorCode ? ` ${colorize('Error Code:', 'dim')} ${logEntry.errorCode}` : '',
|
|
137
|
+
` ${colorize('Duration:', 'dim')} ${logEntry.durationMs}ms`,
|
|
138
|
+
].filter(Boolean).join('\n');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Default format
|
|
142
|
+
return `${levelText} ${timeText} ${colorize(logEntry.type || 'LOG', 'white')} ${JSON.stringify(logEntry)}`;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function matchesFilters(logEntry) {
|
|
146
|
+
// Type filter
|
|
147
|
+
if (options.type) {
|
|
148
|
+
const typeFilter = options.type.toLowerCase();
|
|
149
|
+
const logType = (logEntry.type || '').toLowerCase();
|
|
150
|
+
|
|
151
|
+
if (typeFilter === 'http' && !logType.startsWith('http')) return false;
|
|
152
|
+
if (typeFilter === 'mcp' && !logType.startsWith('mcp')) return false;
|
|
153
|
+
if (typeFilter !== 'http' && typeFilter !== 'mcp' && logType !== typeFilter) return false;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Error filter
|
|
157
|
+
if (options.errors && logEntry.level !== 'error' && !logEntry.type?.includes('error')) {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Symbol filter
|
|
162
|
+
if (options.symbol) {
|
|
163
|
+
const logText = JSON.stringify(logEntry).toLowerCase();
|
|
164
|
+
if (!logText.includes(options.symbol.toLowerCase())) return false;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Tool filter
|
|
168
|
+
if (options.tool && logEntry.tool?.toLowerCase() !== options.tool.toLowerCase()) {
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Correlation ID filter
|
|
173
|
+
if (options.correlationId && logEntry.correlationId !== options.correlationId) {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function processLogs(content) {
|
|
181
|
+
const lines = content.trim().split('\n').filter(Boolean);
|
|
182
|
+
const entries = [];
|
|
183
|
+
|
|
184
|
+
for (const line of lines) {
|
|
185
|
+
try {
|
|
186
|
+
const logEntry = JSON.parse(line);
|
|
187
|
+
if (matchesFilters(logEntry)) {
|
|
188
|
+
entries.push(logEntry);
|
|
189
|
+
}
|
|
190
|
+
} catch (e) {
|
|
191
|
+
// Skip invalid JSON lines
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Apply "last N" filter
|
|
196
|
+
const filteredEntries = options.last ? entries.slice(-options.last) : entries;
|
|
197
|
+
|
|
198
|
+
return filteredEntries;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function displayLogs(entries) {
|
|
202
|
+
if (entries.length === 0) {
|
|
203
|
+
console.log(colorize('No logs found matching the filters.', 'yellow'));
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
console.log(colorize(`\n${'='.repeat(80)}`, 'gray'));
|
|
208
|
+
console.log(colorize(` Found ${entries.length} log entries`, 'bright'));
|
|
209
|
+
console.log(colorize(`${'='.repeat(80)}\n`, 'gray'));
|
|
210
|
+
|
|
211
|
+
for (const entry of entries) {
|
|
212
|
+
console.log(formatLog(entry));
|
|
213
|
+
console.log(''); // Blank line between entries
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function printHelp() {
|
|
218
|
+
console.log(`
|
|
219
|
+
${colorize('Zebpay MCP Server Log Viewer', 'bright')}
|
|
220
|
+
|
|
221
|
+
${colorize('Usage:', 'cyan')}
|
|
222
|
+
node scripts/log-viewer.js [options]
|
|
223
|
+
|
|
224
|
+
${colorize('Options:', 'cyan')}
|
|
225
|
+
--type=TYPE Filter by log type (http, mcp, http_request, http_response, etc.)
|
|
226
|
+
--errors Show only errors
|
|
227
|
+
--symbol=SYMBOL Filter by trading symbol (e.g., BTC-INR)
|
|
228
|
+
--tool=TOOL Filter by MCP tool name (e.g., placeMarketOrder)
|
|
229
|
+
--correlation=ID Filter by correlation ID
|
|
230
|
+
--last=N Show last N entries
|
|
231
|
+
--watch Watch mode (live tail)
|
|
232
|
+
--raw Show raw JSON output
|
|
233
|
+
--help Show this help message
|
|
234
|
+
|
|
235
|
+
${colorize('Examples:', 'cyan')}
|
|
236
|
+
node scripts/log-viewer.js --type=http --last=20
|
|
237
|
+
node scripts/log-viewer.js --errors
|
|
238
|
+
node scripts/log-viewer.js --symbol=BTC-INR --watch
|
|
239
|
+
node scripts/log-viewer.js --tool=zebpay_spot_placeMarketOrder
|
|
240
|
+
node scripts/log-viewer.js --correlation=abc123def456
|
|
241
|
+
`);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Main
|
|
245
|
+
if (hasFlag('help')) {
|
|
246
|
+
printHelp();
|
|
247
|
+
process.exit(0);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (!existsSync(logPath)) {
|
|
251
|
+
console.error(colorize(`Error: Log file not found at ${logPath}`, 'red'));
|
|
252
|
+
console.error(colorize('Make sure the server has been started and logs are being written.', 'yellow'));
|
|
253
|
+
process.exit(1);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (options.watch) {
|
|
257
|
+
console.log(colorize(`Watching ${logPath}...`, 'cyan'));
|
|
258
|
+
console.log(colorize('Press Ctrl+C to exit\n', 'gray'));
|
|
259
|
+
|
|
260
|
+
let lastSize = 0;
|
|
261
|
+
|
|
262
|
+
watchFile(logPath, { interval: 500 }, (curr, prev) => {
|
|
263
|
+
if (curr.size > lastSize) {
|
|
264
|
+
const content = readFileSync(logPath, 'utf-8');
|
|
265
|
+
const newContent = content.slice(lastSize);
|
|
266
|
+
lastSize = curr.size;
|
|
267
|
+
|
|
268
|
+
const entries = processLogs(newContent);
|
|
269
|
+
if (entries.length > 0) {
|
|
270
|
+
for (const entry of entries) {
|
|
271
|
+
console.log(formatLog(entry));
|
|
272
|
+
console.log('');
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
// Display existing logs first
|
|
279
|
+
const content = readFileSync(logPath, 'utf-8');
|
|
280
|
+
lastSize = content.length;
|
|
281
|
+
const entries = processLogs(content);
|
|
282
|
+
displayLogs(entries);
|
|
283
|
+
} else {
|
|
284
|
+
const content = readFileSync(logPath, 'utf-8');
|
|
285
|
+
const entries = processLogs(content);
|
|
286
|
+
displayLogs(entries);
|
|
287
|
+
}
|
|
288
|
+
|
package/server.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
|
+
"name": "io.github.zebpay/zebpay-mcp-server",
|
|
4
|
+
"title": "ZebPay MCP Server",
|
|
5
|
+
"description": "MCP server for ZebPay crypto exchange — fetch prices, balances, and manage orders",
|
|
6
|
+
"repository": {
|
|
7
|
+
"url": "https://github.com/zebpay/zebpay-mcp-server",
|
|
8
|
+
"source": "github",
|
|
9
|
+
"id": "YOUR_GITHUB_REPO_ID"
|
|
10
|
+
},
|
|
11
|
+
"version": "1.0.0",
|
|
12
|
+
"packages": [
|
|
13
|
+
{
|
|
14
|
+
"registryType": "npm",
|
|
15
|
+
"identifier": "@zebpay_rajesh/zebpay-mcp-server",
|
|
16
|
+
"version": "1.0.0",
|
|
17
|
+
"transport": {
|
|
18
|
+
"type": "stdio"
|
|
19
|
+
},
|
|
20
|
+
"environmentVariables": [
|
|
21
|
+
{
|
|
22
|
+
"name": "ZEBPAY_API_KEY",
|
|
23
|
+
"description": "Your ZebPay API key from the developer portal",
|
|
24
|
+
"isRequired": true,
|
|
25
|
+
"format": "string",
|
|
26
|
+
"isSecret": true
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Error handling tests for MCP error utilities.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { createInvalidParamsError, createInternalError, convertHttpErrorToMcpError } from "../mcp/errors.js";
|
|
6
|
+
import { ErrorCode } from "@modelcontextprotocol/sdk/types.js";
|
|
7
|
+
|
|
8
|
+
// Basic test suite (requires jest to be installed)
|
|
9
|
+
describe("MCP Error Handling", () => {
|
|
10
|
+
describe("createInvalidParamsError", () => {
|
|
11
|
+
it("should create an InvalidParams error with message", () => {
|
|
12
|
+
const error = createInvalidParamsError("Invalid symbol format");
|
|
13
|
+
|
|
14
|
+
if (!(error instanceof Error)) {
|
|
15
|
+
throw new Error("Expected error to be instance of Error");
|
|
16
|
+
}
|
|
17
|
+
if (error.code !== ErrorCode.InvalidParams) {
|
|
18
|
+
throw new Error(`Expected error code ${ErrorCode.InvalidParams}, got ${error.code}`);
|
|
19
|
+
}
|
|
20
|
+
// MCP error messages include code prefix: "MCP error -32602: <message>"
|
|
21
|
+
if (!error.message.includes("Invalid symbol format")) {
|
|
22
|
+
throw new Error(`Expected error message to contain "Invalid symbol format", got "${error.message}"`);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("should include data when provided", () => {
|
|
27
|
+
const data = { symbol: "BTC-INR" };
|
|
28
|
+
const error = createInvalidParamsError("Invalid symbol", data);
|
|
29
|
+
|
|
30
|
+
if (JSON.stringify(error.data) !== JSON.stringify(data)) {
|
|
31
|
+
throw new Error(`Expected error data to match ${JSON.stringify(data)}`);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe("createInternalError", () => {
|
|
37
|
+
it("should create an InternalError with message", () => {
|
|
38
|
+
const error = createInternalError("Server error occurred");
|
|
39
|
+
|
|
40
|
+
if (!(error instanceof Error)) {
|
|
41
|
+
throw new Error("Expected error to be instance of Error");
|
|
42
|
+
}
|
|
43
|
+
if (error.code !== ErrorCode.InternalError) {
|
|
44
|
+
throw new Error(`Expected error code ${ErrorCode.InternalError}, got ${error.code}`);
|
|
45
|
+
}
|
|
46
|
+
// MCP error messages include code prefix: "MCP error -32603: <message>"
|
|
47
|
+
if (!error.message.includes("Server error occurred")) {
|
|
48
|
+
throw new Error(`Expected error message to contain "Server error occurred", got "${error.message}"`);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("should include data when provided", () => {
|
|
53
|
+
const data = { status: 500 };
|
|
54
|
+
const error = createInternalError("Server error", data);
|
|
55
|
+
|
|
56
|
+
if (JSON.stringify(error.data) !== JSON.stringify(data)) {
|
|
57
|
+
throw new Error(`Expected error data to match ${JSON.stringify(data)}`);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe("convertHttpErrorToMcpError", () => {
|
|
63
|
+
it("should convert 400 to InvalidParams", () => {
|
|
64
|
+
const error = convertHttpErrorToMcpError(400, "Bad request", {});
|
|
65
|
+
|
|
66
|
+
if (error.code !== ErrorCode.InvalidParams) {
|
|
67
|
+
throw new Error(`Expected InvalidParams, got ${error.code}`);
|
|
68
|
+
}
|
|
69
|
+
// The message should contain the original message or a transformed version
|
|
70
|
+
if (!error.message.toLowerCase().includes("bad request") && !error.message.toLowerCase().includes("invalid")) {
|
|
71
|
+
throw new Error(`Expected message to contain "bad request" or "invalid", got "${error.message}"`);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("should convert 401 to InvalidParams with auth message", () => {
|
|
76
|
+
const error = convertHttpErrorToMcpError(401, "Unauthorized", {});
|
|
77
|
+
|
|
78
|
+
if (error.code !== ErrorCode.InvalidParams) {
|
|
79
|
+
throw new Error(`Expected InvalidParams, got ${error.code}`);
|
|
80
|
+
}
|
|
81
|
+
if (!error.message.toLowerCase().includes("credential")) {
|
|
82
|
+
throw new Error(`Expected message to contain "credential", got "${error.message}"`);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("should convert 403 to InvalidParams with auth message", () => {
|
|
87
|
+
const error = convertHttpErrorToMcpError(403, "Forbidden", {});
|
|
88
|
+
|
|
89
|
+
if (error.code !== ErrorCode.InvalidParams) {
|
|
90
|
+
throw new Error(`Expected InvalidParams, got ${error.code}`);
|
|
91
|
+
}
|
|
92
|
+
if (!error.message.toLowerCase().includes("credential")) {
|
|
93
|
+
throw new Error(`Expected message to contain "credential", got "${error.message}"`);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("should convert 404 to InvalidParams", () => {
|
|
98
|
+
const error = convertHttpErrorToMcpError(404, "Not found", {});
|
|
99
|
+
|
|
100
|
+
if (error.code !== ErrorCode.InvalidParams) {
|
|
101
|
+
throw new Error(`Expected InvalidParams, got ${error.code}`);
|
|
102
|
+
}
|
|
103
|
+
if (!error.message.toLowerCase().includes("not found")) {
|
|
104
|
+
throw new Error(`Expected message to contain "not found", got "${error.message}"`);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("should convert 429 to InternalError with retryable flag", () => {
|
|
109
|
+
const error = convertHttpErrorToMcpError(429, "Rate limit", {});
|
|
110
|
+
|
|
111
|
+
if (error.code !== ErrorCode.InternalError) {
|
|
112
|
+
throw new Error(`Expected InternalError, got ${error.code}`);
|
|
113
|
+
}
|
|
114
|
+
if (!error.message.toLowerCase().includes("rate limit")) {
|
|
115
|
+
throw new Error(`Expected message to contain "rate limit", got "${error.message}"`);
|
|
116
|
+
}
|
|
117
|
+
const data = error.data as { retryable?: boolean };
|
|
118
|
+
if (!data?.retryable) {
|
|
119
|
+
throw new Error("Expected retryable flag to be true");
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("should convert 500 to InternalError with retryable flag", () => {
|
|
124
|
+
const error = convertHttpErrorToMcpError(500, "Server error", {});
|
|
125
|
+
|
|
126
|
+
if (error.code !== ErrorCode.InternalError) {
|
|
127
|
+
throw new Error(`Expected InternalError, got ${error.code}`);
|
|
128
|
+
}
|
|
129
|
+
if (!error.message.toLowerCase().includes("server error")) {
|
|
130
|
+
throw new Error(`Expected message to contain "server error", got "${error.message}"`);
|
|
131
|
+
}
|
|
132
|
+
const data = error.data as { retryable?: boolean };
|
|
133
|
+
if (!data?.retryable) {
|
|
134
|
+
throw new Error("Expected retryable flag to be true");
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("should convert network error (status 0) to InternalError", () => {
|
|
139
|
+
const error = convertHttpErrorToMcpError(0, "Network error", {});
|
|
140
|
+
|
|
141
|
+
if (error.code !== ErrorCode.InternalError) {
|
|
142
|
+
throw new Error(`Expected InternalError, got ${error.code}`);
|
|
143
|
+
}
|
|
144
|
+
if (!error.message.toLowerCase().includes("network")) {
|
|
145
|
+
throw new Error(`Expected message to contain "network", got "${error.message}"`);
|
|
146
|
+
}
|
|
147
|
+
const data = error.data as { retryable?: boolean };
|
|
148
|
+
if (!data?.retryable) {
|
|
149
|
+
throw new Error("Expected retryable flag to be true");
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("should handle generic HTTP error messages", () => {
|
|
154
|
+
const error = convertHttpErrorToMcpError(400, "HTTP 400", {});
|
|
155
|
+
|
|
156
|
+
if (error.code !== ErrorCode.InvalidParams) {
|
|
157
|
+
throw new Error(`Expected InvalidParams, got ${error.code}`);
|
|
158
|
+
}
|
|
159
|
+
if (error.message === "HTTP 400") {
|
|
160
|
+
throw new Error("Expected message to be transformed, not generic HTTP 400");
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it("should preserve detailed error messages", () => {
|
|
165
|
+
const detailedMessage = "Symbol BTC-INR not found in exchange";
|
|
166
|
+
const error = convertHttpErrorToMcpError(404, detailedMessage, {});
|
|
167
|
+
|
|
168
|
+
if (!error.message.includes(detailedMessage)) {
|
|
169
|
+
throw new Error(`Expected message to contain "${detailedMessage}", got "${error.message}"`);
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// Helper functions for tests (if jest is not available, these provide basic functionality)
|
|
176
|
+
declare global {
|
|
177
|
+
function describe(name: string, fn: () => void): void;
|
|
178
|
+
function it(name: string, fn: () => void): void;
|
|
179
|
+
}
|
|
180
|
+
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Tests for MCP prompts registration and functionality.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, beforeEach, afterEach, jest } from "@jest/globals";
|
|
6
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
7
|
+
import { PublicClient } from "../public/PublicClient.js";
|
|
8
|
+
import { SpotClient } from "../private/SpotClient.js";
|
|
9
|
+
import { AppConfig } from "../config.js";
|
|
10
|
+
import { registerPrompts } from "../mcp/prompts.js";
|
|
11
|
+
|
|
12
|
+
describe("MCP Prompts", () => {
|
|
13
|
+
let mockServer: McpServer;
|
|
14
|
+
let mockPublicClient: PublicClient;
|
|
15
|
+
let mockSpotClient: SpotClient | null;
|
|
16
|
+
let mockConfig: AppConfig;
|
|
17
|
+
let originalConsoleError: typeof console.error;
|
|
18
|
+
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
// Suppress console.error during tests
|
|
21
|
+
originalConsoleError = console.error;
|
|
22
|
+
console.error = jest.fn();
|
|
23
|
+
|
|
24
|
+
// Create mock server
|
|
25
|
+
mockServer = new McpServer({
|
|
26
|
+
name: "test-server",
|
|
27
|
+
version: "1.0.0",
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// Create mock public client
|
|
31
|
+
mockPublicClient = {} as unknown as PublicClient;
|
|
32
|
+
|
|
33
|
+
// Create mock spot client
|
|
34
|
+
mockSpotClient = {} as unknown as SpotClient;
|
|
35
|
+
|
|
36
|
+
// Create mock config
|
|
37
|
+
mockConfig = {
|
|
38
|
+
spotBaseUrl: "https://test.api",
|
|
39
|
+
futuresBaseUrl: "https://test.futures.api",
|
|
40
|
+
marketBaseUrl: "https://test.market.api",
|
|
41
|
+
transports: ["stdio"],
|
|
42
|
+
logLevel: "info",
|
|
43
|
+
signingHeaders: {
|
|
44
|
+
apiKeyHeader: "X-AUTH-APIKEY",
|
|
45
|
+
signatureHeader: "X-AUTH-SIGNATURE",
|
|
46
|
+
timestampHeader: "",
|
|
47
|
+
},
|
|
48
|
+
timeoutMs: 15000,
|
|
49
|
+
retryCount: 2,
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
afterEach(() => {
|
|
54
|
+
// Restore console.error after tests
|
|
55
|
+
console.error = originalConsoleError;
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("should register check-balance-before-trade prompt when spot client is available", () => {
|
|
59
|
+
expect(() => {
|
|
60
|
+
registerPrompts(mockServer, mockSpotClient, mockPublicClient, mockConfig);
|
|
61
|
+
}).not.toThrow();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("should register analyze-market prompt", () => {
|
|
65
|
+
expect(() => {
|
|
66
|
+
registerPrompts(mockServer, null, mockPublicClient, mockConfig);
|
|
67
|
+
}).not.toThrow();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("should register compare-trading-pairs prompt", () => {
|
|
71
|
+
expect(() => {
|
|
72
|
+
registerPrompts(mockServer, null, mockPublicClient, mockConfig);
|
|
73
|
+
}).not.toThrow();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("should register get-exchange-info prompt", () => {
|
|
77
|
+
expect(() => {
|
|
78
|
+
registerPrompts(mockServer, null, mockPublicClient, mockConfig);
|
|
79
|
+
}).not.toThrow();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("should not register authenticated prompts when spot client is null", () => {
|
|
83
|
+
// Public prompts should still register successfully without spot client
|
|
84
|
+
expect(() => {
|
|
85
|
+
registerPrompts(mockServer, null, mockPublicClient, mockConfig);
|
|
86
|
+
}).not.toThrow();
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Tests for MCP resources registration and functionality.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, beforeEach, afterEach, jest } from "@jest/globals";
|
|
6
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
7
|
+
import { PublicClient } from "../public/PublicClient.js";
|
|
8
|
+
import { SpotClient } from "../private/SpotClient.js";
|
|
9
|
+
import { AppConfig } from "../config.js";
|
|
10
|
+
import { registerResources } from "../mcp/resources.js";
|
|
11
|
+
|
|
12
|
+
describe("MCP Resources", () => {
|
|
13
|
+
let mockServer: McpServer;
|
|
14
|
+
let mockPublicClient: PublicClient;
|
|
15
|
+
let mockSpotClient: SpotClient | null;
|
|
16
|
+
let mockConfig: AppConfig;
|
|
17
|
+
let originalConsoleError: typeof console.error;
|
|
18
|
+
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
// Suppress console.error during tests
|
|
21
|
+
originalConsoleError = console.error;
|
|
22
|
+
console.error = jest.fn();
|
|
23
|
+
|
|
24
|
+
// Create mock server
|
|
25
|
+
mockServer = new McpServer({
|
|
26
|
+
name: "test-server",
|
|
27
|
+
version: "1.0.0",
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// Create mock public client
|
|
31
|
+
mockPublicClient = {
|
|
32
|
+
getExchangeInfo: jest.fn<() => Promise<unknown>>().mockResolvedValue({ symbols: [] }),
|
|
33
|
+
getCurrencies: jest.fn<() => Promise<unknown>>().mockResolvedValue({ currencies: [] }),
|
|
34
|
+
getAllTickers: jest.fn<() => Promise<unknown>>().mockResolvedValue({ tickers: [] }),
|
|
35
|
+
} as unknown as PublicClient;
|
|
36
|
+
|
|
37
|
+
// Create mock spot client
|
|
38
|
+
mockSpotClient = {
|
|
39
|
+
getBalance: jest.fn<(currencies?: string) => Promise<unknown>>().mockResolvedValue({ balances: [] }),
|
|
40
|
+
} as unknown as SpotClient;
|
|
41
|
+
|
|
42
|
+
// Create mock config
|
|
43
|
+
mockConfig = {
|
|
44
|
+
spotBaseUrl: "https://test.api",
|
|
45
|
+
futuresBaseUrl: "https://test.futures.api",
|
|
46
|
+
marketBaseUrl: "https://test.market.api",
|
|
47
|
+
transports: ["stdio"],
|
|
48
|
+
logLevel: "info",
|
|
49
|
+
signingHeaders: {
|
|
50
|
+
apiKeyHeader: "X-AUTH-APIKEY",
|
|
51
|
+
signatureHeader: "X-AUTH-SIGNATURE",
|
|
52
|
+
timestampHeader: "",
|
|
53
|
+
},
|
|
54
|
+
timeoutMs: 15000,
|
|
55
|
+
retryCount: 2,
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
afterEach(() => {
|
|
60
|
+
// Restore console.error after tests
|
|
61
|
+
console.error = originalConsoleError;
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("should register exchange-info resource", () => {
|
|
65
|
+
expect(() => {
|
|
66
|
+
registerResources(mockServer, mockPublicClient, null, mockConfig);
|
|
67
|
+
}).not.toThrow();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("should register currencies resource", () => {
|
|
71
|
+
expect(() => {
|
|
72
|
+
registerResources(mockServer, mockPublicClient, null, mockConfig);
|
|
73
|
+
}).not.toThrow();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("should register all-tickers resource", () => {
|
|
77
|
+
expect(() => {
|
|
78
|
+
registerResources(mockServer, mockPublicClient, null, mockConfig);
|
|
79
|
+
}).not.toThrow();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("should register balance resource when spot client is available", () => {
|
|
83
|
+
expect(() => {
|
|
84
|
+
registerResources(mockServer, mockPublicClient, mockSpotClient, mockConfig);
|
|
85
|
+
}).not.toThrow();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("should not register balance resource when spot client is null", () => {
|
|
89
|
+
// Resources should still register successfully without spot client
|
|
90
|
+
expect(() => {
|
|
91
|
+
registerResources(mockServer, mockPublicClient, null, mockConfig);
|
|
92
|
+
}).not.toThrow();
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|