@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,557 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* V3 MCP HTTP Transport
|
|
3
|
+
*
|
|
4
|
+
* HTTP/REST transport for MCP communication:
|
|
5
|
+
* - Express-based with optimized middleware
|
|
6
|
+
* - WebSocket support for real-time notifications
|
|
7
|
+
* - Connection pooling for client connections
|
|
8
|
+
* - Security headers with helmet
|
|
9
|
+
*
|
|
10
|
+
* Performance Targets:
|
|
11
|
+
* - Request handling: <20ms overhead
|
|
12
|
+
* - WebSocket message: <5ms
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { EventEmitter } from 'events';
|
|
16
|
+
import express, { Express, Request, Response, NextFunction } from 'express';
|
|
17
|
+
import { createServer, Server } from 'http';
|
|
18
|
+
import { WebSocketServer, WebSocket } from 'ws';
|
|
19
|
+
import cors from 'cors';
|
|
20
|
+
import helmet from 'helmet';
|
|
21
|
+
import {
|
|
22
|
+
ITransport,
|
|
23
|
+
TransportType,
|
|
24
|
+
MCPRequest,
|
|
25
|
+
MCPResponse,
|
|
26
|
+
MCPNotification,
|
|
27
|
+
RequestHandler,
|
|
28
|
+
NotificationHandler,
|
|
29
|
+
TransportHealthStatus,
|
|
30
|
+
ILogger,
|
|
31
|
+
AuthConfig,
|
|
32
|
+
} from '../types.js';
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* HTTP Transport Configuration
|
|
36
|
+
*/
|
|
37
|
+
export interface HttpTransportConfig {
|
|
38
|
+
host: string;
|
|
39
|
+
port: number;
|
|
40
|
+
tlsEnabled?: boolean;
|
|
41
|
+
tlsCert?: string;
|
|
42
|
+
tlsKey?: string;
|
|
43
|
+
corsEnabled?: boolean;
|
|
44
|
+
corsOrigins?: string[];
|
|
45
|
+
auth?: AuthConfig;
|
|
46
|
+
maxRequestSize?: string;
|
|
47
|
+
requestTimeout?: number;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* HTTP Transport Implementation
|
|
52
|
+
*/
|
|
53
|
+
export class HttpTransport extends EventEmitter implements ITransport {
|
|
54
|
+
public readonly type: TransportType = 'http';
|
|
55
|
+
|
|
56
|
+
private requestHandler?: RequestHandler;
|
|
57
|
+
private notificationHandler?: NotificationHandler;
|
|
58
|
+
private app: Express;
|
|
59
|
+
private server?: Server;
|
|
60
|
+
private wss?: WebSocketServer;
|
|
61
|
+
private running = false;
|
|
62
|
+
private activeConnections = new Set<WebSocket>();
|
|
63
|
+
|
|
64
|
+
// Statistics
|
|
65
|
+
private messagesReceived = 0;
|
|
66
|
+
private messagesSent = 0;
|
|
67
|
+
private errors = 0;
|
|
68
|
+
private httpRequests = 0;
|
|
69
|
+
private wsMessages = 0;
|
|
70
|
+
|
|
71
|
+
constructor(
|
|
72
|
+
private readonly logger: ILogger,
|
|
73
|
+
private readonly config: HttpTransportConfig
|
|
74
|
+
) {
|
|
75
|
+
super();
|
|
76
|
+
this.app = express();
|
|
77
|
+
this.setupMiddleware();
|
|
78
|
+
this.setupRoutes();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Start the transport
|
|
83
|
+
*/
|
|
84
|
+
async start(): Promise<void> {
|
|
85
|
+
if (this.running) {
|
|
86
|
+
throw new Error('HTTP transport already running');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
this.logger.info('Starting HTTP transport', {
|
|
90
|
+
host: this.config.host,
|
|
91
|
+
port: this.config.port,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Create HTTP server
|
|
95
|
+
this.server = createServer(this.app);
|
|
96
|
+
|
|
97
|
+
// Create WebSocket server
|
|
98
|
+
this.wss = new WebSocketServer({
|
|
99
|
+
server: this.server,
|
|
100
|
+
path: '/ws',
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
this.setupWebSocketHandlers();
|
|
104
|
+
|
|
105
|
+
// Start server
|
|
106
|
+
await new Promise<void>((resolve, reject) => {
|
|
107
|
+
this.server!.listen(this.config.port, this.config.host, () => {
|
|
108
|
+
resolve();
|
|
109
|
+
});
|
|
110
|
+
this.server!.on('error', reject);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
this.running = true;
|
|
114
|
+
this.logger.info('HTTP transport started', {
|
|
115
|
+
url: `http://${this.config.host}:${this.config.port}`,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Stop the transport
|
|
121
|
+
*/
|
|
122
|
+
async stop(): Promise<void> {
|
|
123
|
+
if (!this.running) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
this.logger.info('Stopping HTTP transport');
|
|
128
|
+
this.running = false;
|
|
129
|
+
|
|
130
|
+
// Close all WebSocket connections
|
|
131
|
+
for (const ws of this.activeConnections) {
|
|
132
|
+
try {
|
|
133
|
+
ws.close(1000, 'Server shutting down');
|
|
134
|
+
} catch {
|
|
135
|
+
// Ignore errors
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
this.activeConnections.clear();
|
|
139
|
+
|
|
140
|
+
// Close WebSocket server
|
|
141
|
+
if (this.wss) {
|
|
142
|
+
this.wss.close();
|
|
143
|
+
this.wss = undefined;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Close HTTP server
|
|
147
|
+
if (this.server) {
|
|
148
|
+
await new Promise<void>((resolve) => {
|
|
149
|
+
this.server!.close(() => resolve());
|
|
150
|
+
});
|
|
151
|
+
this.server = undefined;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
this.logger.info('HTTP transport stopped');
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Register request handler
|
|
159
|
+
*/
|
|
160
|
+
onRequest(handler: RequestHandler): void {
|
|
161
|
+
this.requestHandler = handler;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Register notification handler
|
|
166
|
+
*/
|
|
167
|
+
onNotification(handler: NotificationHandler): void {
|
|
168
|
+
this.notificationHandler = handler;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Get health status
|
|
173
|
+
*/
|
|
174
|
+
async getHealthStatus(): Promise<TransportHealthStatus> {
|
|
175
|
+
return {
|
|
176
|
+
healthy: this.running,
|
|
177
|
+
metrics: {
|
|
178
|
+
messagesReceived: this.messagesReceived,
|
|
179
|
+
messagesSent: this.messagesSent,
|
|
180
|
+
errors: this.errors,
|
|
181
|
+
httpRequests: this.httpRequests,
|
|
182
|
+
wsMessages: this.wsMessages,
|
|
183
|
+
activeConnections: this.activeConnections.size,
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Send notification to all connected WebSocket clients
|
|
190
|
+
*/
|
|
191
|
+
async sendNotification(notification: MCPNotification): Promise<void> {
|
|
192
|
+
const message = JSON.stringify(notification);
|
|
193
|
+
|
|
194
|
+
for (const ws of this.activeConnections) {
|
|
195
|
+
try {
|
|
196
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
197
|
+
ws.send(message);
|
|
198
|
+
this.messagesSent++;
|
|
199
|
+
}
|
|
200
|
+
} catch (error) {
|
|
201
|
+
this.logger.error('Failed to send notification', { error });
|
|
202
|
+
this.errors++;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Setup Express middleware
|
|
209
|
+
*/
|
|
210
|
+
private setupMiddleware(): void {
|
|
211
|
+
// Security headers
|
|
212
|
+
this.app.use(helmet({
|
|
213
|
+
contentSecurityPolicy: false, // Allow for flexibility
|
|
214
|
+
}));
|
|
215
|
+
|
|
216
|
+
// CORS - Secure defaults (no wildcard in production)
|
|
217
|
+
if (this.config.corsEnabled !== false) {
|
|
218
|
+
const allowedOrigins = this.config.corsOrigins;
|
|
219
|
+
|
|
220
|
+
// SECURITY: Reject wildcard CORS in production unless explicitly configured
|
|
221
|
+
if (!allowedOrigins || allowedOrigins.length === 0) {
|
|
222
|
+
this.logger.warn('CORS: No origins configured, restricting to same-origin only');
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
this.app.use(cors({
|
|
226
|
+
origin: (origin, callback) => {
|
|
227
|
+
// Allow requests with no origin (same-origin, curl, etc.)
|
|
228
|
+
if (!origin) {
|
|
229
|
+
callback(null, true);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Check against allowed origins
|
|
234
|
+
if (allowedOrigins && allowedOrigins.length > 0) {
|
|
235
|
+
if (allowedOrigins.includes(origin) || allowedOrigins.includes('*')) {
|
|
236
|
+
callback(null, true);
|
|
237
|
+
} else {
|
|
238
|
+
callback(new Error(`CORS: Origin '${origin}' not allowed`));
|
|
239
|
+
}
|
|
240
|
+
} else {
|
|
241
|
+
// No origins configured - reject cross-origin requests
|
|
242
|
+
callback(new Error('CORS: Cross-origin requests not allowed'));
|
|
243
|
+
}
|
|
244
|
+
},
|
|
245
|
+
credentials: true,
|
|
246
|
+
maxAge: 86400,
|
|
247
|
+
methods: ['GET', 'POST', 'OPTIONS'],
|
|
248
|
+
allowedHeaders: ['Content-Type', 'Authorization', 'X-Request-ID'],
|
|
249
|
+
}));
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Body parsing
|
|
253
|
+
this.app.use(express.json({
|
|
254
|
+
limit: this.config.maxRequestSize || '10mb',
|
|
255
|
+
}));
|
|
256
|
+
|
|
257
|
+
// Request timeout
|
|
258
|
+
if (this.config.requestTimeout) {
|
|
259
|
+
this.app.use((req, res, next) => {
|
|
260
|
+
res.setTimeout(this.config.requestTimeout!, () => {
|
|
261
|
+
res.status(408).json({
|
|
262
|
+
jsonrpc: '2.0',
|
|
263
|
+
id: null,
|
|
264
|
+
error: { code: -32000, message: 'Request timeout' },
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
next();
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Request logging
|
|
272
|
+
this.app.use((req, res, next) => {
|
|
273
|
+
const startTime = performance.now();
|
|
274
|
+
res.on('finish', () => {
|
|
275
|
+
const duration = performance.now() - startTime;
|
|
276
|
+
this.logger.debug('HTTP request', {
|
|
277
|
+
method: req.method,
|
|
278
|
+
path: req.path,
|
|
279
|
+
status: res.statusCode,
|
|
280
|
+
duration: `${duration.toFixed(2)}ms`,
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
next();
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Setup Express routes
|
|
289
|
+
*/
|
|
290
|
+
private setupRoutes(): void {
|
|
291
|
+
// Health check
|
|
292
|
+
this.app.get('/health', (req, res) => {
|
|
293
|
+
res.json({
|
|
294
|
+
status: 'ok',
|
|
295
|
+
timestamp: new Date().toISOString(),
|
|
296
|
+
connections: this.activeConnections.size,
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
// MCP JSON-RPC endpoint
|
|
301
|
+
this.app.post('/rpc', async (req, res) => {
|
|
302
|
+
await this.handleHttpRequest(req, res);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
// Alternative MCP endpoint
|
|
306
|
+
this.app.post('/mcp', async (req, res) => {
|
|
307
|
+
await this.handleHttpRequest(req, res);
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
// Server info
|
|
311
|
+
this.app.get('/info', (req, res) => {
|
|
312
|
+
res.json({
|
|
313
|
+
name: 'Claude-Flow MCP Server V3',
|
|
314
|
+
version: '3.0.0',
|
|
315
|
+
transport: 'http',
|
|
316
|
+
capabilities: {
|
|
317
|
+
jsonrpc: true,
|
|
318
|
+
websocket: true,
|
|
319
|
+
},
|
|
320
|
+
});
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
// 404 handler
|
|
324
|
+
this.app.use((req, res) => {
|
|
325
|
+
res.status(404).json({
|
|
326
|
+
error: 'Not found',
|
|
327
|
+
path: req.path,
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
// Error handler
|
|
332
|
+
this.app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
|
|
333
|
+
this.logger.error('Express error', { error: err });
|
|
334
|
+
this.errors++;
|
|
335
|
+
res.status(500).json({
|
|
336
|
+
jsonrpc: '2.0',
|
|
337
|
+
id: null,
|
|
338
|
+
error: { code: -32603, message: 'Internal error' },
|
|
339
|
+
});
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Setup WebSocket handlers
|
|
345
|
+
*/
|
|
346
|
+
private setupWebSocketHandlers(): void {
|
|
347
|
+
if (!this.wss) return;
|
|
348
|
+
|
|
349
|
+
this.wss.on('connection', (ws, req) => {
|
|
350
|
+
this.activeConnections.add(ws);
|
|
351
|
+
this.logger.info('WebSocket client connected', {
|
|
352
|
+
total: this.activeConnections.size,
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
ws.on('message', async (data) => {
|
|
356
|
+
await this.handleWebSocketMessage(ws, data.toString());
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
ws.on('close', () => {
|
|
360
|
+
this.activeConnections.delete(ws);
|
|
361
|
+
this.logger.info('WebSocket client disconnected', {
|
|
362
|
+
total: this.activeConnections.size,
|
|
363
|
+
});
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
ws.on('error', (error) => {
|
|
367
|
+
this.logger.error('WebSocket error', { error });
|
|
368
|
+
this.errors++;
|
|
369
|
+
this.activeConnections.delete(ws);
|
|
370
|
+
});
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Handle HTTP request
|
|
376
|
+
*/
|
|
377
|
+
private async handleHttpRequest(req: Request, res: Response): Promise<void> {
|
|
378
|
+
this.httpRequests++;
|
|
379
|
+
this.messagesReceived++;
|
|
380
|
+
|
|
381
|
+
// Validate authentication (ALWAYS check, not just when explicitly enabled)
|
|
382
|
+
// SECURITY: Auth should be opt-out, not opt-in
|
|
383
|
+
const requiresAuth = this.config.auth?.enabled !== false; // Default to requiring auth
|
|
384
|
+
|
|
385
|
+
if (requiresAuth && this.config.auth) {
|
|
386
|
+
const authResult = this.validateAuth(req);
|
|
387
|
+
if (!authResult.valid) {
|
|
388
|
+
this.logger.warn('Authentication failed', {
|
|
389
|
+
ip: req.ip,
|
|
390
|
+
path: req.path,
|
|
391
|
+
error: authResult.error,
|
|
392
|
+
});
|
|
393
|
+
res.status(401).json({
|
|
394
|
+
jsonrpc: '2.0',
|
|
395
|
+
id: null,
|
|
396
|
+
error: { code: -32001, message: 'Unauthorized' },
|
|
397
|
+
});
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
} else if (requiresAuth && !this.config.auth) {
|
|
401
|
+
// No auth configured but auth is required - warn and continue (development mode)
|
|
402
|
+
this.logger.warn('No authentication configured - running in development mode');
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
const message = req.body;
|
|
406
|
+
|
|
407
|
+
// Validate JSON-RPC format
|
|
408
|
+
if (message.jsonrpc !== '2.0') {
|
|
409
|
+
res.status(400).json({
|
|
410
|
+
jsonrpc: '2.0',
|
|
411
|
+
id: message.id || null,
|
|
412
|
+
error: { code: -32600, message: 'Invalid JSON-RPC version' },
|
|
413
|
+
});
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (!message.method) {
|
|
418
|
+
res.status(400).json({
|
|
419
|
+
jsonrpc: '2.0',
|
|
420
|
+
id: message.id || null,
|
|
421
|
+
error: { code: -32600, message: 'Missing method' },
|
|
422
|
+
});
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Handle notification vs request
|
|
427
|
+
if (message.id === undefined) {
|
|
428
|
+
// Notification
|
|
429
|
+
if (this.notificationHandler) {
|
|
430
|
+
await this.notificationHandler(message as MCPNotification);
|
|
431
|
+
}
|
|
432
|
+
res.status(204).end();
|
|
433
|
+
} else {
|
|
434
|
+
// Request
|
|
435
|
+
if (!this.requestHandler) {
|
|
436
|
+
res.status(500).json({
|
|
437
|
+
jsonrpc: '2.0',
|
|
438
|
+
id: message.id,
|
|
439
|
+
error: { code: -32603, message: 'No request handler' },
|
|
440
|
+
});
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
try {
|
|
445
|
+
const response = await this.requestHandler(message as MCPRequest);
|
|
446
|
+
res.json(response);
|
|
447
|
+
this.messagesSent++;
|
|
448
|
+
} catch (error) {
|
|
449
|
+
this.errors++;
|
|
450
|
+
res.status(500).json({
|
|
451
|
+
jsonrpc: '2.0',
|
|
452
|
+
id: message.id,
|
|
453
|
+
error: {
|
|
454
|
+
code: -32603,
|
|
455
|
+
message: error instanceof Error ? error.message : 'Internal error',
|
|
456
|
+
},
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Handle WebSocket message
|
|
464
|
+
*/
|
|
465
|
+
private async handleWebSocketMessage(ws: WebSocket, data: string): Promise<void> {
|
|
466
|
+
this.wsMessages++;
|
|
467
|
+
this.messagesReceived++;
|
|
468
|
+
|
|
469
|
+
try {
|
|
470
|
+
const message = JSON.parse(data);
|
|
471
|
+
|
|
472
|
+
if (message.jsonrpc !== '2.0') {
|
|
473
|
+
ws.send(JSON.stringify({
|
|
474
|
+
jsonrpc: '2.0',
|
|
475
|
+
id: message.id || null,
|
|
476
|
+
error: { code: -32600, message: 'Invalid JSON-RPC version' },
|
|
477
|
+
}));
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
if (message.id === undefined) {
|
|
482
|
+
// Notification
|
|
483
|
+
if (this.notificationHandler) {
|
|
484
|
+
await this.notificationHandler(message as MCPNotification);
|
|
485
|
+
}
|
|
486
|
+
} else {
|
|
487
|
+
// Request
|
|
488
|
+
if (!this.requestHandler) {
|
|
489
|
+
ws.send(JSON.stringify({
|
|
490
|
+
jsonrpc: '2.0',
|
|
491
|
+
id: message.id,
|
|
492
|
+
error: { code: -32603, message: 'No request handler' },
|
|
493
|
+
}));
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
const response = await this.requestHandler(message as MCPRequest);
|
|
498
|
+
ws.send(JSON.stringify(response));
|
|
499
|
+
this.messagesSent++;
|
|
500
|
+
}
|
|
501
|
+
} catch (error) {
|
|
502
|
+
this.errors++;
|
|
503
|
+
this.logger.error('WebSocket message error', { error });
|
|
504
|
+
|
|
505
|
+
try {
|
|
506
|
+
const parsed = JSON.parse(data);
|
|
507
|
+
ws.send(JSON.stringify({
|
|
508
|
+
jsonrpc: '2.0',
|
|
509
|
+
id: parsed.id || null,
|
|
510
|
+
error: { code: -32700, message: 'Parse error' },
|
|
511
|
+
}));
|
|
512
|
+
} catch {
|
|
513
|
+
ws.send(JSON.stringify({
|
|
514
|
+
jsonrpc: '2.0',
|
|
515
|
+
id: null,
|
|
516
|
+
error: { code: -32700, message: 'Parse error' },
|
|
517
|
+
}));
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Validate authentication
|
|
524
|
+
*/
|
|
525
|
+
private validateAuth(req: Request): { valid: boolean; error?: string } {
|
|
526
|
+
const auth = req.headers.authorization;
|
|
527
|
+
|
|
528
|
+
if (!auth) {
|
|
529
|
+
return { valid: false, error: 'Authorization header required' };
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
const tokenMatch = auth.match(/^Bearer\s+(.+)$/i);
|
|
533
|
+
if (!tokenMatch) {
|
|
534
|
+
return { valid: false, error: 'Invalid authorization format' };
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
const token = tokenMatch[1];
|
|
538
|
+
|
|
539
|
+
if (this.config.auth?.tokens?.length) {
|
|
540
|
+
if (!this.config.auth.tokens.includes(token)) {
|
|
541
|
+
return { valid: false, error: 'Invalid token' };
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
return { valid: true };
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/**
|
|
550
|
+
* Create HTTP transport
|
|
551
|
+
*/
|
|
552
|
+
export function createHttpTransport(
|
|
553
|
+
logger: ILogger,
|
|
554
|
+
config: HttpTransportConfig
|
|
555
|
+
): HttpTransport {
|
|
556
|
+
return new HttpTransport(logger, config);
|
|
557
|
+
}
|