@mseep/mcp-agent-social 1.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.
Files changed (165) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +154 -0
  3. package/bin/mcp-agent-social +30 -0
  4. package/dist/api-client.d.ts +31 -0
  5. package/dist/api-client.d.ts.map +1 -0
  6. package/dist/api-client.js +212 -0
  7. package/dist/api-client.js.map +1 -0
  8. package/dist/config.d.ts +19 -0
  9. package/dist/config.d.ts.map +1 -0
  10. package/dist/config.js +79 -0
  11. package/dist/config.js.map +1 -0
  12. package/dist/hooks/index.d.ts +38 -0
  13. package/dist/hooks/index.d.ts.map +1 -0
  14. package/dist/hooks/index.js +253 -0
  15. package/dist/hooks/index.js.map +1 -0
  16. package/dist/hooks/types.d.ts +35 -0
  17. package/dist/hooks/types.d.ts.map +1 -0
  18. package/dist/hooks/types.js +4 -0
  19. package/dist/hooks/types.js.map +1 -0
  20. package/dist/http-server.d.ts +38 -0
  21. package/dist/http-server.d.ts.map +1 -0
  22. package/dist/http-server.js +210 -0
  23. package/dist/http-server.js.map +1 -0
  24. package/dist/index.d.ts +2 -0
  25. package/dist/index.d.ts.map +1 -0
  26. package/dist/index.js +186 -0
  27. package/dist/index.js.map +1 -0
  28. package/dist/logger.d.ts +44 -0
  29. package/dist/logger.d.ts.map +1 -0
  30. package/dist/logger.js +281 -0
  31. package/dist/logger.js.map +1 -0
  32. package/dist/metrics.d.ts +47 -0
  33. package/dist/metrics.d.ts.map +1 -0
  34. package/dist/metrics.js +178 -0
  35. package/dist/metrics.js.map +1 -0
  36. package/dist/middleware/error-handler.d.ts +74 -0
  37. package/dist/middleware/error-handler.d.ts.map +1 -0
  38. package/dist/middleware/error-handler.js +218 -0
  39. package/dist/middleware/error-handler.js.map +1 -0
  40. package/dist/middleware/index.d.ts +55 -0
  41. package/dist/middleware/index.d.ts.map +1 -0
  42. package/dist/middleware/index.js +91 -0
  43. package/dist/middleware/index.js.map +1 -0
  44. package/dist/middleware/timeout.d.ts +52 -0
  45. package/dist/middleware/timeout.d.ts.map +1 -0
  46. package/dist/middleware/timeout.js +189 -0
  47. package/dist/middleware/timeout.js.map +1 -0
  48. package/dist/middleware/validator.d.ts +25 -0
  49. package/dist/middleware/validator.d.ts.map +1 -0
  50. package/dist/middleware/validator.js +186 -0
  51. package/dist/middleware/validator.js.map +1 -0
  52. package/dist/prompts/analyze.d.ts +46 -0
  53. package/dist/prompts/analyze.d.ts.map +1 -0
  54. package/dist/prompts/analyze.js +351 -0
  55. package/dist/prompts/analyze.js.map +1 -0
  56. package/dist/prompts/generate.d.ts +48 -0
  57. package/dist/prompts/generate.d.ts.map +1 -0
  58. package/dist/prompts/generate.js +177 -0
  59. package/dist/prompts/generate.js.map +1 -0
  60. package/dist/prompts/index.d.ts +23 -0
  61. package/dist/prompts/index.d.ts.map +1 -0
  62. package/dist/prompts/index.js +69 -0
  63. package/dist/prompts/index.js.map +1 -0
  64. package/dist/prompts/summarize.d.ts +32 -0
  65. package/dist/prompts/summarize.d.ts.map +1 -0
  66. package/dist/prompts/summarize.js +182 -0
  67. package/dist/prompts/summarize.js.map +1 -0
  68. package/dist/prompts/types.d.ts +34 -0
  69. package/dist/prompts/types.d.ts.map +1 -0
  70. package/dist/prompts/types.js +24 -0
  71. package/dist/prompts/types.js.map +1 -0
  72. package/dist/resources/agents.d.ts +17 -0
  73. package/dist/resources/agents.d.ts.map +1 -0
  74. package/dist/resources/agents.js +139 -0
  75. package/dist/resources/agents.js.map +1 -0
  76. package/dist/resources/feed.d.ts +19 -0
  77. package/dist/resources/feed.d.ts.map +1 -0
  78. package/dist/resources/feed.js +138 -0
  79. package/dist/resources/feed.js.map +1 -0
  80. package/dist/resources/index.d.ts +19 -0
  81. package/dist/resources/index.d.ts.map +1 -0
  82. package/dist/resources/index.js +146 -0
  83. package/dist/resources/index.js.map +1 -0
  84. package/dist/resources/posts.d.ts +17 -0
  85. package/dist/resources/posts.d.ts.map +1 -0
  86. package/dist/resources/posts.js +151 -0
  87. package/dist/resources/posts.js.map +1 -0
  88. package/dist/resources/types.d.ts +91 -0
  89. package/dist/resources/types.d.ts.map +1 -0
  90. package/dist/resources/types.js +12 -0
  91. package/dist/resources/types.js.map +1 -0
  92. package/dist/roots/index.d.ts +43 -0
  93. package/dist/roots/index.d.ts.map +1 -0
  94. package/dist/roots/index.js +131 -0
  95. package/dist/roots/index.js.map +1 -0
  96. package/dist/roots/types.d.ts +31 -0
  97. package/dist/roots/types.d.ts.map +1 -0
  98. package/dist/roots/types.js +4 -0
  99. package/dist/roots/types.js.map +1 -0
  100. package/dist/session-manager.d.ts +50 -0
  101. package/dist/session-manager.d.ts.map +1 -0
  102. package/dist/session-manager.js +127 -0
  103. package/dist/session-manager.js.map +1 -0
  104. package/dist/tools/create-post.d.ts +45 -0
  105. package/dist/tools/create-post.d.ts.map +1 -0
  106. package/dist/tools/create-post.js +119 -0
  107. package/dist/tools/create-post.js.map +1 -0
  108. package/dist/tools/index.d.ts +13 -0
  109. package/dist/tools/index.d.ts.map +1 -0
  110. package/dist/tools/index.js +44 -0
  111. package/dist/tools/index.js.map +1 -0
  112. package/dist/tools/login.d.ts +35 -0
  113. package/dist/tools/login.d.ts.map +1 -0
  114. package/dist/tools/login.js +132 -0
  115. package/dist/tools/login.js.map +1 -0
  116. package/dist/tools/read-posts.d.ts +48 -0
  117. package/dist/tools/read-posts.d.ts.map +1 -0
  118. package/dist/tools/read-posts.js +93 -0
  119. package/dist/tools/read-posts.js.map +1 -0
  120. package/dist/types.d.ts +88 -0
  121. package/dist/types.d.ts.map +1 -0
  122. package/dist/types.js +4 -0
  123. package/dist/types.js.map +1 -0
  124. package/dist/utils/json.d.ts +13 -0
  125. package/dist/utils/json.d.ts.map +1 -0
  126. package/dist/utils/json.js +48 -0
  127. package/dist/utils/json.js.map +1 -0
  128. package/dist/validation.d.ts +58 -0
  129. package/dist/validation.d.ts.map +1 -0
  130. package/dist/validation.js +223 -0
  131. package/dist/validation.js.map +1 -0
  132. package/package.json +70 -0
  133. package/src/api-client.ts +292 -0
  134. package/src/config.ts +92 -0
  135. package/src/hooks/index.ts +304 -0
  136. package/src/hooks/types.ts +44 -0
  137. package/src/http-server.ts +243 -0
  138. package/src/index.ts +213 -0
  139. package/src/logger.ts +326 -0
  140. package/src/metrics.ts +235 -0
  141. package/src/middleware/error-handler.ts +252 -0
  142. package/src/middleware/index.ts +112 -0
  143. package/src/middleware/timeout.ts +216 -0
  144. package/src/middleware/validator.ts +216 -0
  145. package/src/prompts/analyze.ts +404 -0
  146. package/src/prompts/generate.ts +217 -0
  147. package/src/prompts/index.ts +121 -0
  148. package/src/prompts/summarize.ts +217 -0
  149. package/src/prompts/types.ts +44 -0
  150. package/src/resources/agents.ts +165 -0
  151. package/src/resources/feed.ts +169 -0
  152. package/src/resources/index.ts +210 -0
  153. package/src/resources/posts.ts +179 -0
  154. package/src/resources/types.ts +104 -0
  155. package/src/roots/index.ts +166 -0
  156. package/src/roots/types.ts +36 -0
  157. package/src/session-manager.ts +149 -0
  158. package/src/tools/create-post.ts +154 -0
  159. package/src/tools/index.ts +70 -0
  160. package/src/tools/login.ts +169 -0
  161. package/src/tools/read-posts.ts +120 -0
  162. package/src/types.ts +107 -0
  163. package/src/utils/json.ts +46 -0
  164. package/src/validation.ts +322 -0
  165. package/tsconfig.json +22 -0
package/src/metrics.ts ADDED
@@ -0,0 +1,235 @@
1
+ // ABOUTME: Basic performance monitoring and metrics collection
2
+ // ABOUTME: Tracks operation timings, memory usage, and system health
3
+
4
+ export interface Metric {
5
+ name: string;
6
+ value: number;
7
+ timestamp: Date;
8
+ tags?: Record<string, string>;
9
+ }
10
+
11
+ export interface OperationMetrics {
12
+ count: number;
13
+ totalDuration: number;
14
+ minDuration: number;
15
+ maxDuration: number;
16
+ averageDuration: number;
17
+ lastDuration: number;
18
+ errors: number;
19
+ }
20
+
21
+ export class MetricsCollector {
22
+ private static instance: MetricsCollector;
23
+ private metrics: Map<string, OperationMetrics>;
24
+ private startTime: number;
25
+ private sessionCount: number;
26
+ private activeOperations: Map<string, number>;
27
+ private cleanupInterval: ReturnType<typeof setInterval> | null = null;
28
+ private readonly OPERATION_TIMEOUT = 5 * 60 * 1000; // 5 minutes
29
+
30
+ private constructor() {
31
+ this.metrics = new Map();
32
+ this.startTime = Date.now();
33
+ this.sessionCount = 0;
34
+ this.activeOperations = new Map();
35
+
36
+ // Set up periodic cleanup of stale operations
37
+ this.cleanupInterval = setInterval(() => this.cleanupStaleOperations(), 60000); // Every minute
38
+ // Allow process to exit even if interval is active (for tests)
39
+ if (this.cleanupInterval.unref) {
40
+ this.cleanupInterval.unref();
41
+ }
42
+ }
43
+
44
+ static getInstance(): MetricsCollector {
45
+ if (!MetricsCollector.instance) {
46
+ MetricsCollector.instance = new MetricsCollector();
47
+ }
48
+ return MetricsCollector.instance;
49
+ }
50
+
51
+ // Start tracking an operation
52
+ startOperation(operationName: string): string {
53
+ const operationId = `${operationName}_${Date.now()}_${Math.random()}`;
54
+ this.activeOperations.set(operationId, Date.now());
55
+ return operationId;
56
+ }
57
+
58
+ // End tracking an operation
59
+ endOperation(operationId: string, success = true): void {
60
+ const startTime = this.activeOperations.get(operationId);
61
+ if (!startTime) {
62
+ return;
63
+ }
64
+
65
+ const duration = Date.now() - startTime;
66
+ this.activeOperations.delete(operationId);
67
+
68
+ // Extract operation name from ID
69
+ const operationName = operationId.split('_')[0];
70
+ this.recordOperation(operationName, duration, success);
71
+ }
72
+
73
+ // Cleanup stale operations that have been running too long
74
+ private cleanupStaleOperations(): void {
75
+ const now = Date.now();
76
+ const staleOperations: string[] = [];
77
+
78
+ for (const [id, startTime] of this.activeOperations.entries()) {
79
+ if (now - startTime > this.OPERATION_TIMEOUT) {
80
+ staleOperations.push(id);
81
+ }
82
+ }
83
+
84
+ // Remove stale operations and record them as timed out
85
+ for (const id of staleOperations) {
86
+ this.activeOperations.delete(id);
87
+ const operationName = id.split('_')[0];
88
+ this.recordOperation(operationName, this.OPERATION_TIMEOUT, false, 'timeout');
89
+ }
90
+ }
91
+
92
+ // Record an operation metric
93
+ private recordOperation(
94
+ name: string,
95
+ duration: number,
96
+ success: boolean,
97
+ _reason?: string,
98
+ ): void {
99
+ let metrics = this.metrics.get(name);
100
+ if (!metrics) {
101
+ metrics = {
102
+ count: 0,
103
+ totalDuration: 0,
104
+ minDuration: Number.POSITIVE_INFINITY,
105
+ maxDuration: 0,
106
+ averageDuration: 0,
107
+ lastDuration: 0,
108
+ errors: 0,
109
+ };
110
+ this.metrics.set(name, metrics);
111
+ }
112
+
113
+ metrics.count++;
114
+ metrics.totalDuration += duration;
115
+ metrics.minDuration = Math.min(metrics.minDuration, duration);
116
+ metrics.maxDuration = Math.max(metrics.maxDuration, duration);
117
+ metrics.averageDuration = metrics.totalDuration / metrics.count;
118
+ metrics.lastDuration = duration;
119
+
120
+ if (!success) {
121
+ metrics.errors++;
122
+ }
123
+ }
124
+
125
+ // Session management metrics
126
+ incrementSessionCount(): void {
127
+ this.sessionCount++;
128
+ }
129
+
130
+ decrementSessionCount(): void {
131
+ this.sessionCount = Math.max(0, this.sessionCount - 1);
132
+ }
133
+
134
+ getSessionCount(): number {
135
+ return this.sessionCount;
136
+ }
137
+
138
+ // Get metrics for a specific operation
139
+ getOperationMetrics(operationName: string): OperationMetrics | undefined {
140
+ return this.metrics.get(operationName);
141
+ }
142
+
143
+ // Get all metrics
144
+ getAllMetrics(): Record<string, OperationMetrics> {
145
+ const result: Record<string, OperationMetrics> = {};
146
+ this.metrics.forEach((value, key) => {
147
+ result[key] = { ...value };
148
+ });
149
+ return result;
150
+ }
151
+
152
+ // Get system metrics
153
+ getSystemMetrics(): {
154
+ uptime: number;
155
+ memoryUsage: ReturnType<typeof process.memoryUsage>;
156
+ sessionCount: number;
157
+ activeOperations: number;
158
+ } {
159
+ return {
160
+ uptime: Math.floor((Date.now() - this.startTime) / 1000),
161
+ memoryUsage: process.memoryUsage(),
162
+ sessionCount: this.sessionCount,
163
+ activeOperations: this.activeOperations.size,
164
+ };
165
+ }
166
+
167
+ // Get formatted summary
168
+ getSummary(): string {
169
+ const system = this.getSystemMetrics();
170
+ const operations = this.getAllMetrics();
171
+
172
+ let summary = '=== System Metrics ===\n';
173
+ summary += `Uptime: ${system.uptime}s\n`;
174
+ summary += `Memory (RSS): ${Math.round(system.memoryUsage.rss / 1024 / 1024)}MB\n`;
175
+ summary += `Memory (Heap Used): ${Math.round(system.memoryUsage.heapUsed / 1024 / 1024)}MB\n`;
176
+ summary += `Active Sessions: ${system.sessionCount}\n`;
177
+ summary += `Active Operations: ${system.activeOperations}\n\n`;
178
+
179
+ summary += '=== Operation Metrics ===\n';
180
+ for (const [name, metrics] of Object.entries(operations)) {
181
+ summary += `${name}:\n`;
182
+ summary += ` Count: ${metrics.count}\n`;
183
+ summary += ` Avg Duration: ${Math.round(metrics.averageDuration)}ms\n`;
184
+ summary += ` Min Duration: ${Math.round(metrics.minDuration)}ms\n`;
185
+ summary += ` Max Duration: ${Math.round(metrics.maxDuration)}ms\n`;
186
+ summary += ` Error Rate: ${((metrics.errors / metrics.count) * 100).toFixed(2)}%\n`;
187
+ }
188
+
189
+ return summary;
190
+ }
191
+
192
+ // Reset all metrics (useful for testing)
193
+ reset(): void {
194
+ this.metrics.clear();
195
+ this.activeOperations.clear();
196
+ this.sessionCount = 0;
197
+
198
+ // Clear the cleanup interval when resetting
199
+ if (this.cleanupInterval) {
200
+ clearInterval(this.cleanupInterval);
201
+ this.cleanupInterval = setInterval(() => this.cleanupStaleOperations(), 60000);
202
+ // Allow process to exit even if interval is active (for tests)
203
+ if (this.cleanupInterval.unref) {
204
+ this.cleanupInterval.unref();
205
+ }
206
+ }
207
+ }
208
+
209
+ // Shutdown the metrics collector (for cleanup)
210
+ shutdown(): void {
211
+ if (this.cleanupInterval) {
212
+ clearInterval(this.cleanupInterval);
213
+ this.cleanupInterval = null;
214
+ }
215
+ }
216
+ }
217
+
218
+ // Export singleton instance
219
+ export const metrics = MetricsCollector.getInstance();
220
+
221
+ // Helper function for timing async operations
222
+ export async function withMetrics<T>(
223
+ operationName: string,
224
+ operation: () => Promise<T>,
225
+ ): Promise<T> {
226
+ const operationId = metrics.startOperation(operationName);
227
+ try {
228
+ const result = await operation();
229
+ metrics.endOperation(operationId, true);
230
+ return result;
231
+ } catch (error) {
232
+ metrics.endOperation(operationId, false);
233
+ throw error;
234
+ }
235
+ }
@@ -0,0 +1,252 @@
1
+ // ABOUTME: Enhanced error handling with context enrichment and proper MCP error formatting
2
+ // ABOUTME: Provides structured error responses and comprehensive error tracking
3
+
4
+ import { logger } from '../logger.js';
5
+
6
+ // Custom error classes for better type safety
7
+ export class McpValidationError extends Error {
8
+ constructor(
9
+ message: string,
10
+ public details?: any,
11
+ ) {
12
+ super(message);
13
+ this.name = 'McpValidationError';
14
+ }
15
+ }
16
+
17
+ export class McpAuthenticationError extends Error {
18
+ constructor(message: string) {
19
+ super(message);
20
+ this.name = 'McpAuthenticationError';
21
+ }
22
+ }
23
+
24
+ export class McpRateLimitError extends Error {
25
+ constructor(
26
+ message: string,
27
+ public retryAfter?: number,
28
+ ) {
29
+ super(message);
30
+ this.name = 'McpRateLimitError';
31
+ }
32
+ }
33
+
34
+ export class McpTimeoutError extends Error {
35
+ constructor(
36
+ message: string,
37
+ public timeout?: number,
38
+ ) {
39
+ super(message);
40
+ this.name = 'McpTimeoutError';
41
+ }
42
+ }
43
+
44
+ export class McpMethodNotFoundError extends Error {
45
+ constructor(
46
+ message: string,
47
+ public method?: string,
48
+ ) {
49
+ super(message);
50
+ this.name = 'McpMethodNotFoundError';
51
+ }
52
+ }
53
+
54
+ export interface ErrorContext {
55
+ sessionId: string;
56
+ requestId: string;
57
+ method: string;
58
+ startTime: number;
59
+ }
60
+
61
+ export interface McpError {
62
+ code: number;
63
+ message: string;
64
+ data?: any;
65
+ }
66
+
67
+ export class ErrorHandler {
68
+ private errorCount = 0;
69
+ private errorsByType = new Map<string, number>();
70
+ private errorsByMethod = new Map<string, number>();
71
+
72
+ /**
73
+ * Handle and enrich errors with context
74
+ */
75
+ async handleError(error: any, request: any, context: ErrorContext): Promise<Error> {
76
+ this.errorCount++;
77
+
78
+ const errorType = error.constructor.name;
79
+ this.errorsByType.set(errorType, (this.errorsByType.get(errorType) || 0) + 1);
80
+ this.errorsByMethod.set(context.method, (this.errorsByMethod.get(context.method) || 0) + 1);
81
+
82
+ // Create enriched error
83
+ const enrichedError = this.createEnrichedError(error, request, context);
84
+
85
+ // Log error with context
86
+ logger.error('Request processing error', {
87
+ error: enrichedError.message,
88
+ errorType,
89
+ method: context.method,
90
+ sessionId: context.sessionId,
91
+ requestId: context.requestId,
92
+ processingTime: Date.now() - context.startTime,
93
+ originalError: error.message,
94
+ stack: error.stack,
95
+ });
96
+
97
+ return enrichedError;
98
+ }
99
+
100
+ /**
101
+ * Create an enriched error with proper MCP formatting
102
+ */
103
+ private createEnrichedError(error: any, request: any, context: ErrorContext): Error {
104
+ let mcpError: McpError;
105
+
106
+ // Handle known error types using proper type checking
107
+ if (error.code && typeof error.code === 'number') {
108
+ // Already an MCP error
109
+ mcpError = {
110
+ code: error.code,
111
+ message: error.message,
112
+ data: error.data,
113
+ };
114
+ } else if (error instanceof McpValidationError) {
115
+ mcpError = {
116
+ code: -32602, // Invalid params
117
+ message: 'Request validation failed',
118
+ data: {
119
+ originalMessage: error.message,
120
+ details: error.details,
121
+ },
122
+ };
123
+ } else if (error instanceof McpTimeoutError) {
124
+ mcpError = {
125
+ code: -32603, // Internal error
126
+ message: 'Request timed out',
127
+ data: {
128
+ timeout: error.timeout,
129
+ method: context.method,
130
+ },
131
+ };
132
+ } else if (error instanceof McpMethodNotFoundError) {
133
+ mcpError = {
134
+ code: -32601, // Method not found
135
+ message: 'Method not found',
136
+ data: {
137
+ method: error.method || request.method,
138
+ },
139
+ };
140
+ } else if (error instanceof McpAuthenticationError) {
141
+ mcpError = {
142
+ code: -32600, // Invalid request
143
+ message: 'Unauthorized request',
144
+ data: {
145
+ sessionId: context.sessionId,
146
+ },
147
+ };
148
+ } else if (error instanceof McpRateLimitError) {
149
+ mcpError = {
150
+ code: -32603, // Internal error
151
+ message: 'Rate limit exceeded',
152
+ data: {
153
+ retryAfter: error.retryAfter || 60,
154
+ },
155
+ };
156
+ } else {
157
+ // Generic internal error
158
+ mcpError = {
159
+ code: -32603, // Internal error
160
+ message: 'Internal server error',
161
+ data: {
162
+ originalMessage: error.message,
163
+ type: error.constructor.name,
164
+ },
165
+ };
166
+ }
167
+
168
+ // Create final error object
169
+ const finalError = new Error(mcpError.message);
170
+ (finalError as any).code = mcpError.code;
171
+ (finalError as any).data = {
172
+ ...mcpError.data,
173
+ context: {
174
+ sessionId: context.sessionId,
175
+ requestId: context.requestId,
176
+ method: context.method,
177
+ timestamp: new Date().toISOString(),
178
+ processingTime: Date.now() - context.startTime,
179
+ },
180
+ };
181
+
182
+ return finalError;
183
+ }
184
+
185
+ /**
186
+ * Format error for MCP response
187
+ */
188
+ formatMcpError(error: any): McpError {
189
+ return {
190
+ code: error.code || -32603,
191
+ message: error.message || 'Internal server error',
192
+ data: error.data || null,
193
+ };
194
+ }
195
+
196
+ /**
197
+ * Check if error is recoverable
198
+ */
199
+ isRecoverableError(error: any): boolean {
200
+ if (!error.code) return false;
201
+
202
+ const recoverableCodes = [
203
+ -32602, // Invalid params - client can fix
204
+ -32601, // Method not found - client can fix
205
+ -32600, // Invalid request - client can fix
206
+ ];
207
+
208
+ return recoverableCodes.includes(error.code);
209
+ }
210
+
211
+ /**
212
+ * Get error statistics
213
+ */
214
+ getStats() {
215
+ return {
216
+ totalErrors: this.errorCount,
217
+ errorsByType: Object.fromEntries(this.errorsByType),
218
+ errorsByMethod: Object.fromEntries(this.errorsByMethod),
219
+ mostCommonError: this.getMostCommonError(),
220
+ errorRate: this.errorCount, // This would be divided by total requests in a real system
221
+ };
222
+ }
223
+
224
+ /**
225
+ * Get the most common error type
226
+ */
227
+ private getMostCommonError(): string | null {
228
+ if (this.errorsByType.size === 0) return null;
229
+
230
+ let maxCount = 0;
231
+ let mostCommon = '';
232
+
233
+ for (const [type, count] of this.errorsByType) {
234
+ if (count > maxCount) {
235
+ maxCount = count;
236
+ mostCommon = type;
237
+ }
238
+ }
239
+
240
+ return mostCommon;
241
+ }
242
+
243
+ /**
244
+ * Clear error statistics
245
+ */
246
+ clearStats(): void {
247
+ this.errorCount = 0;
248
+ this.errorsByType.clear();
249
+ this.errorsByMethod.clear();
250
+ logger.info('Error statistics cleared');
251
+ }
252
+ }
@@ -0,0 +1,112 @@
1
+ // ABOUTME: Enhanced middleware for protocol-level validation, error handling, and timeouts
2
+ // ABOUTME: Provides comprehensive request/response processing and error management
3
+
4
+ import { logger } from '../logger.js';
5
+ import { ErrorHandler } from './error-handler.js';
6
+ import { TimeoutManager } from './timeout.js';
7
+ import { RequestValidator } from './validator.js';
8
+
9
+ export interface MiddlewareContext {
10
+ sessionId: string;
11
+ startTime: number;
12
+ requestId: string;
13
+ method: string;
14
+ }
15
+
16
+ export class ProtocolMiddleware {
17
+ private validator: RequestValidator;
18
+ private errorHandler: ErrorHandler;
19
+ private timeoutManager: TimeoutManager;
20
+
21
+ constructor() {
22
+ this.validator = new RequestValidator();
23
+ this.errorHandler = new ErrorHandler();
24
+ this.timeoutManager = new TimeoutManager();
25
+
26
+ logger.info('Protocol middleware initialized');
27
+ }
28
+
29
+ /**
30
+ * Process request through validation and preprocessing
31
+ */
32
+ async processRequest(request: any, context: MiddlewareContext): Promise<any> {
33
+ const startTime = Date.now();
34
+
35
+ try {
36
+ // 1. Validate request structure
37
+ await this.validator.validateRequest(request);
38
+
39
+ // 2. Set up timeout
40
+ const timeoutPromise = this.timeoutManager.createTimeout(context.method);
41
+
42
+ // 3. Process with timeout
43
+ const processedRequest = await Promise.race([
44
+ this.doProcessRequest(request, context),
45
+ timeoutPromise,
46
+ ]);
47
+
48
+ logger.debug('Request processed successfully', {
49
+ method: context.method,
50
+ sessionId: context.sessionId,
51
+ processingTime: Date.now() - startTime,
52
+ });
53
+
54
+ return processedRequest;
55
+ } catch (error) {
56
+ const processedError = await this.errorHandler.handleError(error, request, context);
57
+ throw processedError;
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Process response through enrichment and validation
63
+ */
64
+ async processResponse(response: any, request: any, context: MiddlewareContext): Promise<any> {
65
+ try {
66
+ // Validate response structure
67
+ await this.validator.validateResponse(response, request.method);
68
+
69
+ // Add processing metadata
70
+ const enrichedResponse = {
71
+ ...response,
72
+ _processingTime: Date.now() - context.startTime,
73
+ _requestId: context.requestId,
74
+ };
75
+
76
+ return enrichedResponse;
77
+ } catch (error) {
78
+ const processedError = await this.errorHandler.handleError(error, request, context);
79
+ throw processedError;
80
+ }
81
+ }
82
+
83
+ private async doProcessRequest(request: any, context: MiddlewareContext): Promise<any> {
84
+ // Add request metadata
85
+ const enrichedRequest = {
86
+ ...request,
87
+ _metadata: {
88
+ requestId: context.requestId,
89
+ sessionId: context.sessionId,
90
+ timestamp: new Date().toISOString(),
91
+ processingStarted: context.startTime,
92
+ },
93
+ };
94
+
95
+ return enrichedRequest;
96
+ }
97
+
98
+ /**
99
+ * Get middleware statistics
100
+ */
101
+ getStats() {
102
+ return {
103
+ validator: this.validator.getStats(),
104
+ errorHandler: this.errorHandler.getStats(),
105
+ timeoutManager: this.timeoutManager.getStats(),
106
+ };
107
+ }
108
+ }
109
+
110
+ export { RequestValidator } from './validator.js';
111
+ export { ErrorHandler } from './error-handler.js';
112
+ export { TimeoutManager } from './timeout.js';