@sparkleideas/shared 3.0.0-alpha.7
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/README.md +323 -0
- package/__tests__/hooks/bash-safety.test.ts +289 -0
- package/__tests__/hooks/file-organization.test.ts +335 -0
- package/__tests__/hooks/git-commit.test.ts +336 -0
- package/__tests__/hooks/index.ts +23 -0
- package/__tests__/hooks/session-hooks.test.ts +357 -0
- package/__tests__/hooks/task-hooks.test.ts +193 -0
- package/docs/EVENTS_IMPLEMENTATION_SUMMARY.md +388 -0
- package/docs/EVENTS_QUICK_REFERENCE.md +470 -0
- package/docs/EVENTS_README.md +352 -0
- package/package.json +39 -0
- package/src/core/config/defaults.ts +207 -0
- package/src/core/config/index.ts +15 -0
- package/src/core/config/loader.ts +271 -0
- package/src/core/config/schema.ts +188 -0
- package/src/core/config/validator.ts +209 -0
- package/src/core/event-bus.ts +236 -0
- package/src/core/index.ts +22 -0
- package/src/core/interfaces/agent.interface.ts +251 -0
- package/src/core/interfaces/coordinator.interface.ts +363 -0
- package/src/core/interfaces/event.interface.ts +267 -0
- package/src/core/interfaces/index.ts +19 -0
- package/src/core/interfaces/memory.interface.ts +332 -0
- package/src/core/interfaces/task.interface.ts +223 -0
- package/src/core/orchestrator/event-coordinator.ts +122 -0
- package/src/core/orchestrator/health-monitor.ts +214 -0
- package/src/core/orchestrator/index.ts +89 -0
- package/src/core/orchestrator/lifecycle-manager.ts +263 -0
- package/src/core/orchestrator/session-manager.ts +279 -0
- package/src/core/orchestrator/task-manager.ts +317 -0
- package/src/events/domain-events.ts +584 -0
- package/src/events/event-store.test.ts +387 -0
- package/src/events/event-store.ts +588 -0
- package/src/events/example-usage.ts +293 -0
- package/src/events/index.ts +90 -0
- package/src/events/projections.ts +561 -0
- package/src/events/state-reconstructor.ts +349 -0
- package/src/events.ts +367 -0
- package/src/hooks/INTEGRATION.md +658 -0
- package/src/hooks/README.md +532 -0
- package/src/hooks/example-usage.ts +499 -0
- package/src/hooks/executor.ts +379 -0
- package/src/hooks/hooks.test.ts +421 -0
- package/src/hooks/index.ts +131 -0
- package/src/hooks/registry.ts +333 -0
- package/src/hooks/safety/bash-safety.ts +604 -0
- package/src/hooks/safety/file-organization.ts +473 -0
- package/src/hooks/safety/git-commit.ts +623 -0
- package/src/hooks/safety/index.ts +46 -0
- package/src/hooks/session-hooks.ts +559 -0
- package/src/hooks/task-hooks.ts +513 -0
- package/src/hooks/types.ts +357 -0
- package/src/hooks/verify-exports.test.ts +125 -0
- package/src/index.ts +195 -0
- package/src/mcp/connection-pool.ts +438 -0
- package/src/mcp/index.ts +183 -0
- package/src/mcp/server.ts +774 -0
- package/src/mcp/session-manager.ts +428 -0
- package/src/mcp/tool-registry.ts +566 -0
- package/src/mcp/transport/http.ts +557 -0
- package/src/mcp/transport/index.ts +294 -0
- package/src/mcp/transport/stdio.ts +324 -0
- package/src/mcp/transport/websocket.ts +484 -0
- package/src/mcp/types.ts +565 -0
- package/src/plugin-interface.ts +663 -0
- package/src/plugin-loader.ts +638 -0
- package/src/plugin-registry.ts +604 -0
- package/src/plugins/index.ts +34 -0
- package/src/plugins/official/hive-mind-plugin.ts +330 -0
- package/src/plugins/official/index.ts +24 -0
- package/src/plugins/official/maestro-plugin.ts +508 -0
- package/src/plugins/types.ts +108 -0
- package/src/resilience/bulkhead.ts +277 -0
- package/src/resilience/circuit-breaker.ts +326 -0
- package/src/resilience/index.ts +26 -0
- package/src/resilience/rate-limiter.ts +420 -0
- package/src/resilience/retry.ts +224 -0
- package/src/security/index.ts +39 -0
- package/src/security/input-validation.ts +265 -0
- package/src/security/secure-random.ts +159 -0
- package/src/services/index.ts +16 -0
- package/src/services/v3-progress.service.ts +505 -0
- package/src/types/agent.types.ts +144 -0
- package/src/types/index.ts +22 -0
- package/src/types/mcp.types.ts +300 -0
- package/src/types/memory.types.ts +263 -0
- package/src/types/swarm.types.ts +255 -0
- package/src/types/task.types.ts +205 -0
- package/src/types.ts +367 -0
- package/src/utils/secure-logger.d.ts +69 -0
- package/src/utils/secure-logger.d.ts.map +1 -0
- package/src/utils/secure-logger.js +208 -0
- package/src/utils/secure-logger.js.map +1 -0
- package/src/utils/secure-logger.ts +257 -0
- package/tmp.json +0 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* V3 MCP WebSocket Transport
|
|
3
|
+
*
|
|
4
|
+
* Standalone WebSocket transport for MCP communication:
|
|
5
|
+
* - Native WebSocket server without HTTP dependency
|
|
6
|
+
* - Binary message support for efficiency
|
|
7
|
+
* - Heartbeat/ping-pong for connection health
|
|
8
|
+
* - Automatic reconnection handling
|
|
9
|
+
*
|
|
10
|
+
* Performance Targets:
|
|
11
|
+
* - Message latency: <3ms
|
|
12
|
+
* - Connection overhead: <10ms
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { EventEmitter } from 'events';
|
|
16
|
+
import { WebSocketServer, WebSocket, RawData } from 'ws';
|
|
17
|
+
import { createServer, Server } from 'http';
|
|
18
|
+
import {
|
|
19
|
+
ITransport,
|
|
20
|
+
TransportType,
|
|
21
|
+
MCPRequest,
|
|
22
|
+
MCPResponse,
|
|
23
|
+
MCPNotification,
|
|
24
|
+
RequestHandler,
|
|
25
|
+
NotificationHandler,
|
|
26
|
+
TransportHealthStatus,
|
|
27
|
+
ILogger,
|
|
28
|
+
AuthConfig,
|
|
29
|
+
} from '../types.js';
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* WebSocket Transport Configuration
|
|
33
|
+
*/
|
|
34
|
+
export interface WebSocketTransportConfig {
|
|
35
|
+
host: string;
|
|
36
|
+
port: number;
|
|
37
|
+
path?: string;
|
|
38
|
+
maxConnections?: number;
|
|
39
|
+
heartbeatInterval?: number;
|
|
40
|
+
heartbeatTimeout?: number;
|
|
41
|
+
maxMessageSize?: number;
|
|
42
|
+
auth?: AuthConfig;
|
|
43
|
+
enableBinaryMode?: boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Client connection info
|
|
48
|
+
*/
|
|
49
|
+
interface ClientConnection {
|
|
50
|
+
id: string;
|
|
51
|
+
ws: WebSocket;
|
|
52
|
+
createdAt: Date;
|
|
53
|
+
lastActivity: Date;
|
|
54
|
+
messageCount: number;
|
|
55
|
+
isAlive: boolean;
|
|
56
|
+
isAuthenticated: boolean;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* WebSocket Transport Implementation
|
|
61
|
+
*/
|
|
62
|
+
export class WebSocketTransport extends EventEmitter implements ITransport {
|
|
63
|
+
public readonly type: TransportType = 'websocket';
|
|
64
|
+
|
|
65
|
+
private requestHandler?: RequestHandler;
|
|
66
|
+
private notificationHandler?: NotificationHandler;
|
|
67
|
+
private server?: Server;
|
|
68
|
+
private wss?: WebSocketServer;
|
|
69
|
+
private clients: Map<string, ClientConnection> = new Map();
|
|
70
|
+
private heartbeatTimer?: NodeJS.Timeout;
|
|
71
|
+
private running = false;
|
|
72
|
+
private connectionCounter = 0;
|
|
73
|
+
|
|
74
|
+
// Statistics
|
|
75
|
+
private messagesReceived = 0;
|
|
76
|
+
private messagesSent = 0;
|
|
77
|
+
private errors = 0;
|
|
78
|
+
private totalConnections = 0;
|
|
79
|
+
|
|
80
|
+
constructor(
|
|
81
|
+
private readonly logger: ILogger,
|
|
82
|
+
private readonly config: WebSocketTransportConfig
|
|
83
|
+
) {
|
|
84
|
+
super();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Start the transport
|
|
89
|
+
*/
|
|
90
|
+
async start(): Promise<void> {
|
|
91
|
+
if (this.running) {
|
|
92
|
+
throw new Error('WebSocket transport already running');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
this.logger.info('Starting WebSocket transport', {
|
|
96
|
+
host: this.config.host,
|
|
97
|
+
port: this.config.port,
|
|
98
|
+
path: this.config.path || '/ws',
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// Create HTTP server for WebSocket upgrade
|
|
102
|
+
this.server = createServer((req, res) => {
|
|
103
|
+
// Simple HTTP response for non-WebSocket requests
|
|
104
|
+
res.writeHead(426, { 'Content-Type': 'text/plain' });
|
|
105
|
+
res.end('Upgrade Required - WebSocket connection expected');
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Create WebSocket server
|
|
109
|
+
this.wss = new WebSocketServer({
|
|
110
|
+
server: this.server,
|
|
111
|
+
path: this.config.path || '/ws',
|
|
112
|
+
maxPayload: this.config.maxMessageSize || 10 * 1024 * 1024,
|
|
113
|
+
perMessageDeflate: true, // Enable compression
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
this.setupWebSocketHandlers();
|
|
117
|
+
this.startHeartbeat();
|
|
118
|
+
|
|
119
|
+
// Start server
|
|
120
|
+
await new Promise<void>((resolve, reject) => {
|
|
121
|
+
this.server!.listen(this.config.port, this.config.host, () => {
|
|
122
|
+
resolve();
|
|
123
|
+
});
|
|
124
|
+
this.server!.on('error', reject);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
this.running = true;
|
|
128
|
+
this.logger.info('WebSocket transport started', {
|
|
129
|
+
url: `ws://${this.config.host}:${this.config.port}${this.config.path || '/ws'}`,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Stop the transport
|
|
135
|
+
*/
|
|
136
|
+
async stop(): Promise<void> {
|
|
137
|
+
if (!this.running) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
this.logger.info('Stopping WebSocket transport');
|
|
142
|
+
this.running = false;
|
|
143
|
+
|
|
144
|
+
this.stopHeartbeat();
|
|
145
|
+
|
|
146
|
+
// Close all client connections
|
|
147
|
+
for (const client of this.clients.values()) {
|
|
148
|
+
try {
|
|
149
|
+
client.ws.close(1000, 'Server shutting down');
|
|
150
|
+
} catch {
|
|
151
|
+
// Ignore errors
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
this.clients.clear();
|
|
155
|
+
|
|
156
|
+
// Close WebSocket server
|
|
157
|
+
if (this.wss) {
|
|
158
|
+
this.wss.close();
|
|
159
|
+
this.wss = undefined;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Close HTTP server
|
|
163
|
+
if (this.server) {
|
|
164
|
+
await new Promise<void>((resolve) => {
|
|
165
|
+
this.server!.close(() => resolve());
|
|
166
|
+
});
|
|
167
|
+
this.server = undefined;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
this.logger.info('WebSocket transport stopped');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Register request handler
|
|
175
|
+
*/
|
|
176
|
+
onRequest(handler: RequestHandler): void {
|
|
177
|
+
this.requestHandler = handler;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Register notification handler
|
|
182
|
+
*/
|
|
183
|
+
onNotification(handler: NotificationHandler): void {
|
|
184
|
+
this.notificationHandler = handler;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Get health status
|
|
189
|
+
*/
|
|
190
|
+
async getHealthStatus(): Promise<TransportHealthStatus> {
|
|
191
|
+
return {
|
|
192
|
+
healthy: this.running,
|
|
193
|
+
metrics: {
|
|
194
|
+
messagesReceived: this.messagesReceived,
|
|
195
|
+
messagesSent: this.messagesSent,
|
|
196
|
+
errors: this.errors,
|
|
197
|
+
activeConnections: this.clients.size,
|
|
198
|
+
totalConnections: this.totalConnections,
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Send notification to all connected clients
|
|
205
|
+
*/
|
|
206
|
+
async sendNotification(notification: MCPNotification): Promise<void> {
|
|
207
|
+
const message = this.serializeMessage(notification);
|
|
208
|
+
|
|
209
|
+
for (const client of this.clients.values()) {
|
|
210
|
+
try {
|
|
211
|
+
if (client.ws.readyState === WebSocket.OPEN) {
|
|
212
|
+
client.ws.send(message);
|
|
213
|
+
this.messagesSent++;
|
|
214
|
+
}
|
|
215
|
+
} catch (error) {
|
|
216
|
+
this.logger.error('Failed to send notification', { clientId: client.id, error });
|
|
217
|
+
this.errors++;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Send notification to specific client
|
|
224
|
+
*/
|
|
225
|
+
async sendToClient(clientId: string, notification: MCPNotification): Promise<boolean> {
|
|
226
|
+
const client = this.clients.get(clientId);
|
|
227
|
+
if (!client || client.ws.readyState !== WebSocket.OPEN) {
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
try {
|
|
232
|
+
client.ws.send(this.serializeMessage(notification));
|
|
233
|
+
this.messagesSent++;
|
|
234
|
+
return true;
|
|
235
|
+
} catch (error) {
|
|
236
|
+
this.logger.error('Failed to send to client', { clientId, error });
|
|
237
|
+
this.errors++;
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Get connected clients
|
|
244
|
+
*/
|
|
245
|
+
getClients(): string[] {
|
|
246
|
+
return Array.from(this.clients.keys());
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Get client info
|
|
251
|
+
*/
|
|
252
|
+
getClientInfo(clientId: string): ClientConnection | undefined {
|
|
253
|
+
return this.clients.get(clientId);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Disconnect specific client
|
|
258
|
+
*/
|
|
259
|
+
disconnectClient(clientId: string, reason = 'Disconnected by server'): boolean {
|
|
260
|
+
const client = this.clients.get(clientId);
|
|
261
|
+
if (!client) {
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
try {
|
|
266
|
+
client.ws.close(1000, reason);
|
|
267
|
+
return true;
|
|
268
|
+
} catch {
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Setup WebSocket handlers
|
|
275
|
+
*/
|
|
276
|
+
private setupWebSocketHandlers(): void {
|
|
277
|
+
if (!this.wss) return;
|
|
278
|
+
|
|
279
|
+
this.wss.on('connection', (ws, req) => {
|
|
280
|
+
// Check max connections
|
|
281
|
+
if (this.config.maxConnections && this.clients.size >= this.config.maxConnections) {
|
|
282
|
+
this.logger.warn('Max connections reached, rejecting client');
|
|
283
|
+
ws.close(1013, 'Server at capacity');
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const clientId = `client-${++this.connectionCounter}`;
|
|
288
|
+
const client: ClientConnection = {
|
|
289
|
+
id: clientId,
|
|
290
|
+
ws,
|
|
291
|
+
createdAt: new Date(),
|
|
292
|
+
lastActivity: new Date(),
|
|
293
|
+
messageCount: 0,
|
|
294
|
+
isAlive: true,
|
|
295
|
+
isAuthenticated: !this.config.auth?.enabled,
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
this.clients.set(clientId, client);
|
|
299
|
+
this.totalConnections++;
|
|
300
|
+
|
|
301
|
+
this.logger.info('Client connected', {
|
|
302
|
+
id: clientId,
|
|
303
|
+
total: this.clients.size,
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
// Setup message handler
|
|
307
|
+
ws.on('message', async (data) => {
|
|
308
|
+
await this.handleMessage(client, data);
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
// Setup pong handler for heartbeat
|
|
312
|
+
ws.on('pong', () => {
|
|
313
|
+
client.isAlive = true;
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
// Setup close handler
|
|
317
|
+
ws.on('close', (code, reason) => {
|
|
318
|
+
this.clients.delete(clientId);
|
|
319
|
+
this.logger.info('Client disconnected', {
|
|
320
|
+
id: clientId,
|
|
321
|
+
code,
|
|
322
|
+
reason: reason.toString(),
|
|
323
|
+
total: this.clients.size,
|
|
324
|
+
});
|
|
325
|
+
this.emit('client:disconnected', clientId);
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
// Setup error handler
|
|
329
|
+
ws.on('error', (error) => {
|
|
330
|
+
this.logger.error('Client error', { id: clientId, error });
|
|
331
|
+
this.errors++;
|
|
332
|
+
this.clients.delete(clientId);
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
this.emit('client:connected', clientId);
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Handle incoming message
|
|
341
|
+
*/
|
|
342
|
+
private async handleMessage(client: ClientConnection, data: RawData): Promise<void> {
|
|
343
|
+
client.lastActivity = new Date();
|
|
344
|
+
client.messageCount++;
|
|
345
|
+
this.messagesReceived++;
|
|
346
|
+
|
|
347
|
+
try {
|
|
348
|
+
const message = this.parseMessage(data);
|
|
349
|
+
|
|
350
|
+
// Check authentication for non-authenticated clients
|
|
351
|
+
if (!client.isAuthenticated && this.config.auth?.enabled) {
|
|
352
|
+
if (message.method !== 'authenticate') {
|
|
353
|
+
client.ws.send(this.serializeMessage({
|
|
354
|
+
jsonrpc: '2.0',
|
|
355
|
+
id: message.id || null,
|
|
356
|
+
error: { code: -32001, message: 'Authentication required' },
|
|
357
|
+
} as MCPResponse));
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (message.jsonrpc !== '2.0') {
|
|
363
|
+
client.ws.send(this.serializeMessage({
|
|
364
|
+
jsonrpc: '2.0',
|
|
365
|
+
id: message.id || null,
|
|
366
|
+
error: { code: -32600, message: 'Invalid JSON-RPC version' },
|
|
367
|
+
} as MCPResponse));
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (message.id === undefined) {
|
|
372
|
+
// Notification
|
|
373
|
+
if (this.notificationHandler) {
|
|
374
|
+
await this.notificationHandler(message as MCPNotification);
|
|
375
|
+
}
|
|
376
|
+
} else {
|
|
377
|
+
// Request
|
|
378
|
+
if (!this.requestHandler) {
|
|
379
|
+
client.ws.send(this.serializeMessage({
|
|
380
|
+
jsonrpc: '2.0',
|
|
381
|
+
id: message.id,
|
|
382
|
+
error: { code: -32603, message: 'No request handler' },
|
|
383
|
+
} as MCPResponse));
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const startTime = performance.now();
|
|
388
|
+
const response = await this.requestHandler(message as MCPRequest);
|
|
389
|
+
const duration = performance.now() - startTime;
|
|
390
|
+
|
|
391
|
+
this.logger.debug('Request processed', {
|
|
392
|
+
clientId: client.id,
|
|
393
|
+
method: message.method,
|
|
394
|
+
duration: `${duration.toFixed(2)}ms`,
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
client.ws.send(this.serializeMessage(response));
|
|
398
|
+
this.messagesSent++;
|
|
399
|
+
}
|
|
400
|
+
} catch (error) {
|
|
401
|
+
this.errors++;
|
|
402
|
+
this.logger.error('Message handling error', { clientId: client.id, error });
|
|
403
|
+
|
|
404
|
+
try {
|
|
405
|
+
client.ws.send(this.serializeMessage({
|
|
406
|
+
jsonrpc: '2.0',
|
|
407
|
+
id: null,
|
|
408
|
+
error: { code: -32700, message: 'Parse error' },
|
|
409
|
+
} as MCPResponse));
|
|
410
|
+
} catch {
|
|
411
|
+
// Ignore send errors
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Parse incoming message
|
|
418
|
+
*/
|
|
419
|
+
private parseMessage(data: RawData): any {
|
|
420
|
+
if (this.config.enableBinaryMode && Buffer.isBuffer(data)) {
|
|
421
|
+
// Could implement binary protocol here
|
|
422
|
+
return JSON.parse(data.toString());
|
|
423
|
+
}
|
|
424
|
+
return JSON.parse(data.toString());
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Serialize outgoing message
|
|
429
|
+
*/
|
|
430
|
+
private serializeMessage(message: MCPResponse | MCPNotification): string | Buffer {
|
|
431
|
+
if (this.config.enableBinaryMode) {
|
|
432
|
+
// Could implement binary protocol here
|
|
433
|
+
return JSON.stringify(message);
|
|
434
|
+
}
|
|
435
|
+
return JSON.stringify(message);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Start heartbeat interval
|
|
440
|
+
*/
|
|
441
|
+
private startHeartbeat(): void {
|
|
442
|
+
const interval = this.config.heartbeatInterval || 30000; // 30 seconds
|
|
443
|
+
const timeout = this.config.heartbeatTimeout || 10000; // 10 seconds
|
|
444
|
+
|
|
445
|
+
this.heartbeatTimer = setInterval(() => {
|
|
446
|
+
for (const client of this.clients.values()) {
|
|
447
|
+
if (!client.isAlive) {
|
|
448
|
+
// Client didn't respond to last ping
|
|
449
|
+
this.logger.warn('Client heartbeat timeout', { id: client.id });
|
|
450
|
+
client.ws.terminate();
|
|
451
|
+
this.clients.delete(client.id);
|
|
452
|
+
continue;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
client.isAlive = false;
|
|
456
|
+
try {
|
|
457
|
+
client.ws.ping();
|
|
458
|
+
} catch {
|
|
459
|
+
// Ignore ping errors
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}, interval);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Stop heartbeat interval
|
|
467
|
+
*/
|
|
468
|
+
private stopHeartbeat(): void {
|
|
469
|
+
if (this.heartbeatTimer) {
|
|
470
|
+
clearInterval(this.heartbeatTimer);
|
|
471
|
+
this.heartbeatTimer = undefined;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Create WebSocket transport
|
|
478
|
+
*/
|
|
479
|
+
export function createWebSocketTransport(
|
|
480
|
+
logger: ILogger,
|
|
481
|
+
config: WebSocketTransportConfig
|
|
482
|
+
): WebSocketTransport {
|
|
483
|
+
return new WebSocketTransport(logger, config);
|
|
484
|
+
}
|