@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,114 @@
1
+ /*
2
+ Credential provider abstraction. Current implementation reads from env vars.
3
+ Secrets are never logged directly; use redact() for any visibility.
4
+ */
5
+
6
+ import { redact } from "../config.js";
7
+
8
+ export interface CredentialsProvider {
9
+ getApiKey(): string;
10
+ getSecretKey(): string;
11
+ }
12
+
13
+ export class EnvCredentialsProvider implements CredentialsProvider {
14
+ private readonly apiKeyEnv: string;
15
+ private readonly secretKeyEnv: string;
16
+
17
+ constructor(
18
+ apiKeyEnvName: string = "ZEBPAY_API_KEY",
19
+ secretKeyEnvName: string = "ZEBPAY_API_SECRET"
20
+ ) {
21
+ this.apiKeyEnv = apiKeyEnvName;
22
+ this.secretKeyEnv = secretKeyEnvName;
23
+ }
24
+
25
+ getApiKey(): string {
26
+ const value = process.env[this.apiKeyEnv];
27
+ if (!value) {
28
+ throw new Error(
29
+ `Missing API key in env ${this.apiKeyEnv}. Configure securely.`
30
+ );
31
+ }
32
+ return value;
33
+ }
34
+
35
+ getSecretKey(): string {
36
+ const value = process.env[this.secretKeyEnv];
37
+ if (!value) {
38
+ throw new Error(
39
+ `Missing API secret in env ${this.secretKeyEnv}. Configure securely.`
40
+ );
41
+ }
42
+ return value;
43
+ }
44
+
45
+ /**
46
+ * Returns safe-to-log shapes for debugging without exposing secrets.
47
+ */
48
+ describeRedacted(): { apiKey: string; secretKey: string } {
49
+ return {
50
+ apiKey: redact(process.env[this.apiKeyEnv] ?? ""),
51
+ secretKey: redact(process.env[this.secretKeyEnv] ?? ""),
52
+ };
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Credentials provider that stores credentials in memory.
58
+ * Used for accepting credentials from client during initialization.
59
+ * Credentials are optional - if not provided, authenticated tools will fail with a clear error.
60
+ */
61
+ export class InMemoryCredentialsProvider implements CredentialsProvider {
62
+ private apiKey: string | undefined;
63
+ private secretKey: string | undefined;
64
+
65
+ constructor(apiKey?: string, secretKey?: string) {
66
+ this.apiKey = apiKey;
67
+ this.secretKey = secretKey;
68
+ }
69
+
70
+ setCredentials(apiKey: string, secretKey: string): void {
71
+ if (!apiKey || !secretKey) {
72
+ throw new Error("API key and secret are required.");
73
+ }
74
+ this.apiKey = apiKey;
75
+ this.secretKey = secretKey;
76
+ }
77
+
78
+ getApiKey(): string {
79
+ if (!this.apiKey) {
80
+ throw new Error(
81
+ "API credentials are required for this operation. Please provide ZEBPAY_API_KEY and ZEBPAY_API_SECRET headers or include credentials in initialization params."
82
+ );
83
+ }
84
+ return this.apiKey;
85
+ }
86
+
87
+ getSecretKey(): string {
88
+ if (!this.secretKey) {
89
+ throw new Error(
90
+ "API credentials are required for this operation. Please provide ZEBPAY_API_KEY and ZEBPAY_API_SECRET headers or include credentials in initialization params."
91
+ );
92
+ }
93
+ return this.secretKey;
94
+ }
95
+
96
+ /**
97
+ * Returns safe-to-log shapes for debugging without exposing secrets.
98
+ */
99
+ describeRedacted(): { apiKey: string; secretKey: string } {
100
+ return {
101
+ apiKey: redact(this.apiKey),
102
+ secretKey: redact(this.secretKey),
103
+ };
104
+ }
105
+
106
+ /**
107
+ * Check if credentials are set
108
+ */
109
+ hasCredentials(): boolean {
110
+ return !!(this.apiKey && this.secretKey);
111
+ }
112
+ }
113
+
114
+
@@ -0,0 +1,98 @@
1
+ /*
2
+ HMAC signing utilities for ZebPay.
3
+ The exact prehash format and header names are configurable via config to match ZebPay docs.
4
+ */
5
+
6
+ import crypto from "node:crypto";
7
+ import { SigningHeaderNames } from "../config.js";
8
+
9
+ export interface SignatureResult {
10
+ signature: string;
11
+ timestamp: string;
12
+ }
13
+
14
+ export interface SigningOptions {
15
+ headers: SigningHeaderNames;
16
+ /** If true, include body in prehash; set false for GETs without body. */
17
+ includeBody?: boolean;
18
+ /** Allows overriding timestamp for testing. */
19
+ now?: () => number;
20
+ }
21
+
22
+ export function stableStringify(value: unknown): string {
23
+ // Deterministic JSON stringify for signing.
24
+ return JSON.stringify(value, Object.keys(value as object).sort());
25
+ }
26
+
27
+ /**
28
+ * Sign a query string using HMAC-SHA256 (Zebpay API format).
29
+ * This matches the Postman script format where query parameters are signed directly.
30
+ */
31
+ export function signQueryString(
32
+ queryString: string,
33
+ secretKey: string
34
+ ): string {
35
+ return crypto.createHmac("sha256", secretKey).update(queryString).digest("hex");
36
+ }
37
+
38
+ /**
39
+ * Sign a JSON payload string using HMAC-SHA256 (Zebpay API format for POST requests).
40
+ * This matches the Postman script format where the JSON body string is signed directly.
41
+ */
42
+ export function signPayloadString(
43
+ payloadString: string,
44
+ secretKey: string
45
+ ): string {
46
+ return crypto.createHmac("sha256", secretKey).update(payloadString).digest("hex");
47
+ }
48
+
49
+ /**
50
+ * Produce an HMAC-SHA256 signature. Default prehash: `${method}\n${path}\n${timestamp}\n${body}`.
51
+ */
52
+ export function signRequest(
53
+ method: string,
54
+ path: string,
55
+ body: unknown,
56
+ secretKey: string,
57
+ opts: SigningOptions
58
+ ): SignatureResult {
59
+ const timestamp = String((opts.now ? opts.now() : Date.now()));
60
+ const useBody = opts.includeBody ?? (method.toUpperCase() !== "GET" && body !== undefined && body !== null);
61
+ const bodyString = useBody ? (typeof body === "string" ? body : stableStringify(body)) : "";
62
+ const prehash = `${method.toUpperCase()}\n${path}\n${timestamp}\n${bodyString}`;
63
+ const signature = crypto.createHmac("sha256", secretKey).update(prehash).digest("hex");
64
+ return { signature, timestamp };
65
+ }
66
+
67
+ export function buildAuthHeaders(
68
+ apiKey: string,
69
+ sig: SignatureResult,
70
+ headerNames: SigningHeaderNames
71
+ ): Record<string, string> {
72
+ const headers: Record<string, string> = {
73
+ [headerNames.apiKeyHeader]: apiKey,
74
+ [headerNames.signatureHeader]: sig.signature,
75
+ };
76
+ // Only include timestamp header if it's configured (for non-query-string signing)
77
+ if (headerNames.timestampHeader) {
78
+ headers[headerNames.timestampHeader] = sig.timestamp;
79
+ }
80
+ return headers;
81
+ }
82
+
83
+ /**
84
+ * Build auth headers for query-string based signing (Zebpay format).
85
+ * Only includes API key and signature headers, no timestamp header.
86
+ */
87
+ export function buildQueryStringAuthHeaders(
88
+ apiKey: string,
89
+ signature: string,
90
+ headerNames: SigningHeaderNames
91
+ ): Record<string, string> {
92
+ return {
93
+ [headerNames.apiKeyHeader]: apiKey,
94
+ [headerNames.signatureHeader]: signature,
95
+ };
96
+ }
97
+
98
+
@@ -0,0 +1,146 @@
1
+ /*
2
+ TypeScript type definitions for API responses.
3
+ These help LLMs understand the structure of tool responses.
4
+ */
5
+
6
+ /**
7
+ * Common response structure for order operations
8
+ */
9
+ export interface OrderResponse {
10
+ orderId?: string;
11
+ clientOrderId?: string;
12
+ symbol: string;
13
+ side: "buy" | "sell";
14
+ type: "market" | "limit";
15
+ quantity: string;
16
+ price?: string;
17
+ status: string;
18
+ executedPrice?: string;
19
+ executedQuantity?: string;
20
+ timestamp?: number;
21
+ [key: string]: unknown; // Allow additional fields from API
22
+ }
23
+
24
+ /**
25
+ * Balance information for a single currency
26
+ */
27
+ export interface CurrencyBalance {
28
+ currency: string;
29
+ available: string;
30
+ locked?: string;
31
+ total?: string;
32
+ [key: string]: unknown; // Allow additional fields from API
33
+ }
34
+
35
+ /**
36
+ * Balance response structure
37
+ */
38
+ export interface BalanceResponse {
39
+ balances?: CurrencyBalance[];
40
+ currencies?: Record<string, CurrencyBalance>;
41
+ [key: string]: unknown; // Allow additional fields from API
42
+ }
43
+
44
+ /**
45
+ * Ticker information for a trading pair
46
+ */
47
+ export interface TickerResponse {
48
+ symbol: string;
49
+ lastPrice: string;
50
+ bidPrice?: string;
51
+ askPrice?: string;
52
+ high24h?: string;
53
+ low24h?: string;
54
+ volume24h?: string;
55
+ priceChange24h?: string;
56
+ priceChangePercent24h?: string;
57
+ timestamp?: number;
58
+ [key: string]: unknown; // Allow additional fields from API
59
+ }
60
+
61
+ /**
62
+ * Order book entry
63
+ */
64
+ export interface OrderBookEntry {
65
+ price: string;
66
+ quantity: string;
67
+ }
68
+
69
+ /**
70
+ * Order book response
71
+ */
72
+ export interface OrderBookResponse {
73
+ symbol: string;
74
+ bids: OrderBookEntry[];
75
+ asks: OrderBookEntry[];
76
+ timestamp?: number;
77
+ [key: string]: unknown; // Allow additional fields from API
78
+ }
79
+
80
+ /**
81
+ * Trade information
82
+ */
83
+ export interface TradeResponse {
84
+ symbol: string;
85
+ price: string;
86
+ quantity: string;
87
+ side: "buy" | "sell";
88
+ timestamp: number;
89
+ [key: string]: unknown; // Allow additional fields from API
90
+ }
91
+
92
+ /**
93
+ * Kline/Candlestick data
94
+ */
95
+ export interface KlineResponse {
96
+ openTime: number;
97
+ open: string;
98
+ high: string;
99
+ low: string;
100
+ close: string;
101
+ volume: string;
102
+ closeTime: number;
103
+ [key: string]: unknown; // Allow additional fields from API
104
+ }
105
+
106
+ /**
107
+ * Exchange information
108
+ */
109
+ export interface ExchangeInfoResponse {
110
+ symbols?: Array<{
111
+ symbol: string;
112
+ baseAsset: string;
113
+ quoteAsset: string;
114
+ status: string;
115
+ [key: string]: unknown;
116
+ }>;
117
+ [key: string]: unknown; // Allow additional fields from API
118
+ }
119
+
120
+ /**
121
+ * Currency information
122
+ */
123
+ export interface CurrencyInfo {
124
+ currency: string;
125
+ name?: string;
126
+ fullName?: string;
127
+ precision?: number;
128
+ type?: string;
129
+ chains?: Array<{
130
+ chainName: string;
131
+ withdrawalFee?: string;
132
+ depositMinSize?: string;
133
+ withdrawalMinSize?: string;
134
+ [key: string]: unknown;
135
+ }>;
136
+ [key: string]: unknown; // Allow additional fields from API
137
+ }
138
+
139
+ /**
140
+ * Currencies response
141
+ */
142
+ export interface CurrenciesResponse {
143
+ currencies?: CurrencyInfo[];
144
+ [key: string]: unknown; // Allow additional fields from API
145
+ }
146
+
@@ -0,0 +1,90 @@
1
+ /*
2
+ Simple in-memory cache for public endpoints to reduce API calls.
3
+ */
4
+
5
+ interface CacheEntry<T> {
6
+ data: T;
7
+ timestamp: number;
8
+ ttl: number;
9
+ }
10
+
11
+ export class SimpleCache<T = unknown> {
12
+ private cache = new Map<string, CacheEntry<T>>();
13
+ private defaultTtl: number;
14
+
15
+ constructor(defaultTtlMs: number = 5000) {
16
+ this.defaultTtl = defaultTtlMs;
17
+ }
18
+
19
+ /**
20
+ * Get cached value if not expired
21
+ */
22
+ get(key: string): T | null {
23
+ const entry = this.cache.get(key);
24
+ if (!entry) {
25
+ return null;
26
+ }
27
+
28
+ const now = Date.now();
29
+ if (now - entry.timestamp > entry.ttl) {
30
+ this.cache.delete(key);
31
+ return null;
32
+ }
33
+
34
+ return entry.data;
35
+ }
36
+
37
+ /**
38
+ * Set cached value with optional TTL
39
+ */
40
+ set(key: string, data: T, ttlMs?: number): void {
41
+ this.cache.set(key, {
42
+ data,
43
+ timestamp: Date.now(),
44
+ ttl: ttlMs || this.defaultTtl,
45
+ });
46
+ }
47
+
48
+ /**
49
+ * Clear expired entries
50
+ */
51
+ clearExpired(): void {
52
+ const now = Date.now();
53
+ for (const [key, entry] of this.cache.entries()) {
54
+ if (now - entry.timestamp > entry.ttl) {
55
+ this.cache.delete(key);
56
+ }
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Clear all cache entries
62
+ */
63
+ clear(): void {
64
+ this.cache.clear();
65
+ }
66
+
67
+ /**
68
+ * Get cache size
69
+ */
70
+ size(): number {
71
+ return this.cache.size;
72
+ }
73
+ }
74
+
75
+ // Cache instances for different endpoint types
76
+ export const tickerCache = new SimpleCache(5000); // 5 seconds for tickers
77
+ export const orderBookCache = new SimpleCache(2000); // 2 seconds for order books
78
+ export const exchangeInfoCache = new SimpleCache(60000); // 1 minute for exchange info
79
+ export const currenciesCache = new SimpleCache(300000); // 5 minutes for currencies
80
+
81
+ // Clean up expired entries periodically
82
+ if (typeof setInterval !== "undefined") {
83
+ setInterval(() => {
84
+ tickerCache.clearExpired();
85
+ orderBookCache.clearExpired();
86
+ exchangeInfoCache.clearExpired();
87
+ currenciesCache.clearExpired();
88
+ }, 10000); // Clean every 10 seconds
89
+ }
90
+
@@ -0,0 +1,88 @@
1
+ /*
2
+ File logging utility for writing logs to both console and file.
3
+ */
4
+
5
+ import { createWriteStream, WriteStream } from "node:fs";
6
+ import { mkdir } from "node:fs/promises";
7
+ import { dirname } from "node:path";
8
+
9
+ class FileLogger {
10
+ private stream: WriteStream | null = null;
11
+ private logPath: string | null = null;
12
+
13
+ async initialize(logPath?: string): Promise<void> {
14
+ if (!logPath) {
15
+ // Default log path
16
+ this.logPath = null;
17
+ return;
18
+ }
19
+
20
+ try {
21
+ // Ensure log directory exists
22
+ const logDir = dirname(logPath);
23
+ await mkdir(logDir, { recursive: true });
24
+
25
+ // Create write stream in append mode
26
+ this.stream = createWriteStream(logPath, { flags: "a" });
27
+ this.logPath = logPath;
28
+
29
+ // Write initial log entry
30
+ this.writeToFile(JSON.stringify({
31
+ level: "info",
32
+ type: "log_init",
33
+ timestamp: new Date().toISOString(),
34
+ message: `Logging initialized to file: ${logPath}`,
35
+ }) + "\n");
36
+ } catch (error) {
37
+ console.error(JSON.stringify({
38
+ level: "error",
39
+ type: "log_init_error",
40
+ timestamp: new Date().toISOString(),
41
+ error: error instanceof Error ? error.message : String(error),
42
+ }));
43
+ // Continue without file logging if file can't be opened
44
+ this.stream = null;
45
+ this.logPath = null;
46
+ }
47
+ }
48
+
49
+ log(message: string): void {
50
+ // Always write to console.error
51
+ console.error(message);
52
+
53
+ // Also write to file if initialized
54
+ if (this.stream) {
55
+ this.writeToFile(message + "\n");
56
+ }
57
+ }
58
+
59
+ private writeToFile(message: string): void {
60
+ if (this.stream) {
61
+ try {
62
+ this.stream.write(message);
63
+ } catch (error) {
64
+ // If file write fails, log to console but don't crash
65
+ console.error(JSON.stringify({
66
+ level: "error",
67
+ type: "log_write_error",
68
+ timestamp: new Date().toISOString(),
69
+ error: error instanceof Error ? error.message : String(error),
70
+ }));
71
+ }
72
+ }
73
+ }
74
+
75
+ async close(): Promise<void> {
76
+ if (this.stream) {
77
+ return new Promise((resolve) => {
78
+ this.stream!.end(() => {
79
+ this.stream = null;
80
+ resolve();
81
+ });
82
+ });
83
+ }
84
+ }
85
+ }
86
+
87
+ export const fileLogger = new FileLogger();
88
+
@@ -0,0 +1,135 @@
1
+ /*
2
+ Basic metrics collection for monitoring tool usage and performance.
3
+ */
4
+
5
+ interface Metric {
6
+ toolName: string;
7
+ timestamp: number;
8
+ durationMs: number;
9
+ success: boolean;
10
+ errorType?: string;
11
+ }
12
+
13
+ class MetricsCollector {
14
+ private metrics: Metric[] = [];
15
+ private maxMetrics = 1000; // Keep last 1000 metrics
16
+
17
+ record(
18
+ toolName: string,
19
+ durationMs: number,
20
+ success: boolean,
21
+ errorType?: string
22
+ ): void {
23
+ this.metrics.push({
24
+ toolName,
25
+ timestamp: Date.now(),
26
+ durationMs,
27
+ success,
28
+ errorType,
29
+ });
30
+
31
+ // Keep only last N metrics
32
+ if (this.metrics.length > this.maxMetrics) {
33
+ this.metrics = this.metrics.slice(-this.maxMetrics);
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Get metrics summary for a tool
39
+ */
40
+ getToolStats(toolName: string, timeWindowMs?: number): {
41
+ count: number;
42
+ successCount: number;
43
+ errorCount: number;
44
+ avgDurationMs: number;
45
+ minDurationMs: number;
46
+ maxDurationMs: number;
47
+ } {
48
+ const now = Date.now();
49
+ const window = timeWindowMs || Infinity;
50
+ const relevant = this.metrics.filter(
51
+ (m) =>
52
+ m.toolName === toolName && now - m.timestamp < window
53
+ );
54
+
55
+ if (relevant.length === 0) {
56
+ return {
57
+ count: 0,
58
+ successCount: 0,
59
+ errorCount: 0,
60
+ avgDurationMs: 0,
61
+ minDurationMs: 0,
62
+ maxDurationMs: 0,
63
+ };
64
+ }
65
+
66
+ const successCount = relevant.filter((m) => m.success).length;
67
+ const durations = relevant.map((m) => m.durationMs);
68
+ const sum = durations.reduce((a, b) => a + b, 0);
69
+
70
+ return {
71
+ count: relevant.length,
72
+ successCount,
73
+ errorCount: relevant.length - successCount,
74
+ avgDurationMs: sum / relevant.length,
75
+ minDurationMs: Math.min(...durations),
76
+ maxDurationMs: Math.max(...durations),
77
+ };
78
+ }
79
+
80
+ /**
81
+ * Get all tool names
82
+ */
83
+ getToolNames(): string[] {
84
+ return Array.from(new Set(this.metrics.map((m) => m.toolName)));
85
+ }
86
+
87
+ /**
88
+ * Get overall stats
89
+ */
90
+ getOverallStats(timeWindowMs?: number): {
91
+ totalRequests: number;
92
+ successRate: number;
93
+ avgDurationMs: number;
94
+ toolStats: Record<string, ReturnType<MetricsCollector["getToolStats"]>>;
95
+ } {
96
+ const now = Date.now();
97
+ const window = timeWindowMs || Infinity;
98
+ const relevant = this.metrics.filter((m) => now - m.timestamp < window);
99
+
100
+ if (relevant.length === 0) {
101
+ return {
102
+ totalRequests: 0,
103
+ successRate: 0,
104
+ avgDurationMs: 0,
105
+ toolStats: {},
106
+ };
107
+ }
108
+
109
+ const successCount = relevant.filter((m) => m.success).length;
110
+ const durations = relevant.map((m) => m.durationMs);
111
+ const sum = durations.reduce((a, b) => a + b, 0);
112
+
113
+ const toolStats: Record<string, ReturnType<typeof this.getToolStats>> = {};
114
+ for (const toolName of this.getToolNames()) {
115
+ toolStats[toolName] = this.getToolStats(toolName, timeWindowMs);
116
+ }
117
+
118
+ return {
119
+ totalRequests: relevant.length,
120
+ successRate: successCount / relevant.length,
121
+ avgDurationMs: sum / relevant.length,
122
+ toolStats,
123
+ };
124
+ }
125
+
126
+ /**
127
+ * Clear all metrics
128
+ */
129
+ clear(): void {
130
+ this.metrics = [];
131
+ }
132
+ }
133
+
134
+ export const metricsCollector = new MetricsCollector();
135
+