@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.
- package/LICENSE +21 -0
- package/README.md +154 -0
- package/bin/mcp-agent-social +30 -0
- package/dist/api-client.d.ts +31 -0
- package/dist/api-client.d.ts.map +1 -0
- package/dist/api-client.js +212 -0
- package/dist/api-client.js.map +1 -0
- package/dist/config.d.ts +19 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +79 -0
- package/dist/config.js.map +1 -0
- package/dist/hooks/index.d.ts +38 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +253 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/types.d.ts +35 -0
- package/dist/hooks/types.d.ts.map +1 -0
- package/dist/hooks/types.js +4 -0
- package/dist/hooks/types.js.map +1 -0
- package/dist/http-server.d.ts +38 -0
- package/dist/http-server.d.ts.map +1 -0
- package/dist/http-server.js +210 -0
- package/dist/http-server.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +186 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +44 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +281 -0
- package/dist/logger.js.map +1 -0
- package/dist/metrics.d.ts +47 -0
- package/dist/metrics.d.ts.map +1 -0
- package/dist/metrics.js +178 -0
- package/dist/metrics.js.map +1 -0
- package/dist/middleware/error-handler.d.ts +74 -0
- package/dist/middleware/error-handler.d.ts.map +1 -0
- package/dist/middleware/error-handler.js +218 -0
- package/dist/middleware/error-handler.js.map +1 -0
- package/dist/middleware/index.d.ts +55 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +91 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/middleware/timeout.d.ts +52 -0
- package/dist/middleware/timeout.d.ts.map +1 -0
- package/dist/middleware/timeout.js +189 -0
- package/dist/middleware/timeout.js.map +1 -0
- package/dist/middleware/validator.d.ts +25 -0
- package/dist/middleware/validator.d.ts.map +1 -0
- package/dist/middleware/validator.js +186 -0
- package/dist/middleware/validator.js.map +1 -0
- package/dist/prompts/analyze.d.ts +46 -0
- package/dist/prompts/analyze.d.ts.map +1 -0
- package/dist/prompts/analyze.js +351 -0
- package/dist/prompts/analyze.js.map +1 -0
- package/dist/prompts/generate.d.ts +48 -0
- package/dist/prompts/generate.d.ts.map +1 -0
- package/dist/prompts/generate.js +177 -0
- package/dist/prompts/generate.js.map +1 -0
- package/dist/prompts/index.d.ts +23 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +69 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/prompts/summarize.d.ts +32 -0
- package/dist/prompts/summarize.d.ts.map +1 -0
- package/dist/prompts/summarize.js +182 -0
- package/dist/prompts/summarize.js.map +1 -0
- package/dist/prompts/types.d.ts +34 -0
- package/dist/prompts/types.d.ts.map +1 -0
- package/dist/prompts/types.js +24 -0
- package/dist/prompts/types.js.map +1 -0
- package/dist/resources/agents.d.ts +17 -0
- package/dist/resources/agents.d.ts.map +1 -0
- package/dist/resources/agents.js +139 -0
- package/dist/resources/agents.js.map +1 -0
- package/dist/resources/feed.d.ts +19 -0
- package/dist/resources/feed.d.ts.map +1 -0
- package/dist/resources/feed.js +138 -0
- package/dist/resources/feed.js.map +1 -0
- package/dist/resources/index.d.ts +19 -0
- package/dist/resources/index.d.ts.map +1 -0
- package/dist/resources/index.js +146 -0
- package/dist/resources/index.js.map +1 -0
- package/dist/resources/posts.d.ts +17 -0
- package/dist/resources/posts.d.ts.map +1 -0
- package/dist/resources/posts.js +151 -0
- package/dist/resources/posts.js.map +1 -0
- package/dist/resources/types.d.ts +91 -0
- package/dist/resources/types.d.ts.map +1 -0
- package/dist/resources/types.js +12 -0
- package/dist/resources/types.js.map +1 -0
- package/dist/roots/index.d.ts +43 -0
- package/dist/roots/index.d.ts.map +1 -0
- package/dist/roots/index.js +131 -0
- package/dist/roots/index.js.map +1 -0
- package/dist/roots/types.d.ts +31 -0
- package/dist/roots/types.d.ts.map +1 -0
- package/dist/roots/types.js +4 -0
- package/dist/roots/types.js.map +1 -0
- package/dist/session-manager.d.ts +50 -0
- package/dist/session-manager.d.ts.map +1 -0
- package/dist/session-manager.js +127 -0
- package/dist/session-manager.js.map +1 -0
- package/dist/tools/create-post.d.ts +45 -0
- package/dist/tools/create-post.d.ts.map +1 -0
- package/dist/tools/create-post.js +119 -0
- package/dist/tools/create-post.js.map +1 -0
- package/dist/tools/index.d.ts +13 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +44 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/login.d.ts +35 -0
- package/dist/tools/login.d.ts.map +1 -0
- package/dist/tools/login.js +132 -0
- package/dist/tools/login.js.map +1 -0
- package/dist/tools/read-posts.d.ts +48 -0
- package/dist/tools/read-posts.d.ts.map +1 -0
- package/dist/tools/read-posts.js +93 -0
- package/dist/tools/read-posts.js.map +1 -0
- package/dist/types.d.ts +88 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +4 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/json.d.ts +13 -0
- package/dist/utils/json.d.ts.map +1 -0
- package/dist/utils/json.js +48 -0
- package/dist/utils/json.js.map +1 -0
- package/dist/validation.d.ts +58 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +223 -0
- package/dist/validation.js.map +1 -0
- package/package.json +70 -0
- package/src/api-client.ts +292 -0
- package/src/config.ts +92 -0
- package/src/hooks/index.ts +304 -0
- package/src/hooks/types.ts +44 -0
- package/src/http-server.ts +243 -0
- package/src/index.ts +213 -0
- package/src/logger.ts +326 -0
- package/src/metrics.ts +235 -0
- package/src/middleware/error-handler.ts +252 -0
- package/src/middleware/index.ts +112 -0
- package/src/middleware/timeout.ts +216 -0
- package/src/middleware/validator.ts +216 -0
- package/src/prompts/analyze.ts +404 -0
- package/src/prompts/generate.ts +217 -0
- package/src/prompts/index.ts +121 -0
- package/src/prompts/summarize.ts +217 -0
- package/src/prompts/types.ts +44 -0
- package/src/resources/agents.ts +165 -0
- package/src/resources/feed.ts +169 -0
- package/src/resources/index.ts +210 -0
- package/src/resources/posts.ts +179 -0
- package/src/resources/types.ts +104 -0
- package/src/roots/index.ts +166 -0
- package/src/roots/types.ts +36 -0
- package/src/session-manager.ts +149 -0
- package/src/tools/create-post.ts +154 -0
- package/src/tools/index.ts +70 -0
- package/src/tools/login.ts +169 -0
- package/src/tools/read-posts.ts +120 -0
- package/src/types.ts +107 -0
- package/src/utils/json.ts +46 -0
- package/src/validation.ts +322 -0
- 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';
|