claude-flow 1.0.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 +612 -0
- package/bin/claude-flow +0 -0
- package/bin/claude-flow-simple +0 -0
- package/bin/claude-flow-typecheck +0 -0
- package/deno.json +84 -0
- package/package.json +45 -0
- package/scripts/check-links.ts +274 -0
- package/scripts/check-performance-regression.ts +168 -0
- package/scripts/claude-sparc.sh +562 -0
- package/scripts/coverage-report.ts +692 -0
- package/scripts/demo-task-system.ts +224 -0
- package/scripts/install.js +72 -0
- package/scripts/test-batch-tasks.ts +29 -0
- package/scripts/test-coordination-features.ts +238 -0
- package/scripts/test-mcp.ts +251 -0
- package/scripts/test-runner.ts +571 -0
- package/scripts/validate-examples.ts +288 -0
- package/src/cli/cli-core.ts +273 -0
- package/src/cli/commands/agent.ts +83 -0
- package/src/cli/commands/config.ts +442 -0
- package/src/cli/commands/help.ts +765 -0
- package/src/cli/commands/index.ts +963 -0
- package/src/cli/commands/mcp.ts +191 -0
- package/src/cli/commands/memory.ts +74 -0
- package/src/cli/commands/monitor.ts +403 -0
- package/src/cli/commands/session.ts +595 -0
- package/src/cli/commands/start.ts +156 -0
- package/src/cli/commands/status.ts +345 -0
- package/src/cli/commands/task.ts +79 -0
- package/src/cli/commands/workflow.ts +763 -0
- package/src/cli/completion.ts +553 -0
- package/src/cli/formatter.ts +310 -0
- package/src/cli/index.ts +211 -0
- package/src/cli/main.ts +23 -0
- package/src/cli/repl.ts +1050 -0
- package/src/cli/simple-cli.js +211 -0
- package/src/cli/simple-cli.ts +211 -0
- package/src/coordination/README.md +400 -0
- package/src/coordination/advanced-scheduler.ts +487 -0
- package/src/coordination/circuit-breaker.ts +366 -0
- package/src/coordination/conflict-resolution.ts +490 -0
- package/src/coordination/dependency-graph.ts +475 -0
- package/src/coordination/index.ts +63 -0
- package/src/coordination/manager.ts +460 -0
- package/src/coordination/messaging.ts +290 -0
- package/src/coordination/metrics.ts +585 -0
- package/src/coordination/resources.ts +322 -0
- package/src/coordination/scheduler.ts +390 -0
- package/src/coordination/work-stealing.ts +224 -0
- package/src/core/config.ts +627 -0
- package/src/core/event-bus.ts +186 -0
- package/src/core/json-persistence.ts +183 -0
- package/src/core/logger.ts +262 -0
- package/src/core/orchestrator-fixed.ts +312 -0
- package/src/core/orchestrator.ts +1234 -0
- package/src/core/persistence.ts +276 -0
- package/src/mcp/auth.ts +438 -0
- package/src/mcp/claude-flow-tools.ts +1280 -0
- package/src/mcp/load-balancer.ts +510 -0
- package/src/mcp/router.ts +240 -0
- package/src/mcp/server.ts +548 -0
- package/src/mcp/session-manager.ts +418 -0
- package/src/mcp/tools.ts +180 -0
- package/src/mcp/transports/base.ts +21 -0
- package/src/mcp/transports/http.ts +457 -0
- package/src/mcp/transports/stdio.ts +254 -0
- package/src/memory/backends/base.ts +22 -0
- package/src/memory/backends/markdown.ts +283 -0
- package/src/memory/backends/sqlite.ts +329 -0
- package/src/memory/cache.ts +238 -0
- package/src/memory/indexer.ts +238 -0
- package/src/memory/manager.ts +572 -0
- package/src/terminal/adapters/base.ts +29 -0
- package/src/terminal/adapters/native.ts +504 -0
- package/src/terminal/adapters/vscode.ts +340 -0
- package/src/terminal/manager.ts +308 -0
- package/src/terminal/pool.ts +271 -0
- package/src/terminal/session.ts +250 -0
- package/src/terminal/vscode-bridge.ts +242 -0
- package/src/utils/errors.ts +231 -0
- package/src/utils/helpers.ts +476 -0
- package/src/utils/types.ts +493 -0
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP transport for MCP
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { ITransport, RequestHandler, NotificationHandler } from './base.ts';
|
|
6
|
+
import { MCPRequest, MCPResponse, MCPNotification, MCPConfig } from '../../utils/types.ts';
|
|
7
|
+
import { ILogger } from '../../core/logger.ts';
|
|
8
|
+
import { MCPTransportError } from '../../utils/errors.ts';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* HTTP transport implementation
|
|
12
|
+
*/
|
|
13
|
+
export class HttpTransport implements ITransport {
|
|
14
|
+
private requestHandler?: RequestHandler;
|
|
15
|
+
private notificationHandler?: NotificationHandler;
|
|
16
|
+
private server?: Deno.HttpServer | undefined;
|
|
17
|
+
private messageCount = 0;
|
|
18
|
+
private notificationCount = 0;
|
|
19
|
+
private running = false;
|
|
20
|
+
private connections = new Set<WebSocket>();
|
|
21
|
+
private activeWebSockets = new Set<WebSocket>();
|
|
22
|
+
|
|
23
|
+
constructor(
|
|
24
|
+
private host: string,
|
|
25
|
+
private port: number,
|
|
26
|
+
private tlsEnabled: boolean,
|
|
27
|
+
private logger: ILogger,
|
|
28
|
+
private config?: MCPConfig,
|
|
29
|
+
) {}
|
|
30
|
+
|
|
31
|
+
async start(): Promise<void> {
|
|
32
|
+
if (this.running) {
|
|
33
|
+
throw new MCPTransportError('Transport already running');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
this.logger.info('Starting HTTP transport', {
|
|
37
|
+
host: this.host,
|
|
38
|
+
port: this.port,
|
|
39
|
+
tls: this.tlsEnabled,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
// Create listener
|
|
44
|
+
const listener = Deno.listen({
|
|
45
|
+
hostname: this.host,
|
|
46
|
+
port: this.port,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
this.server = Deno.serve({
|
|
50
|
+
port: this.port,
|
|
51
|
+
hostname: this.host,
|
|
52
|
+
handler: (request) => this.handleRequest(request),
|
|
53
|
+
onListen: ({ hostname, port }) => {
|
|
54
|
+
this.logger.info(`HTTP server listening on ${hostname}:${port}`);
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
this.running = true;
|
|
59
|
+
this.logger.info('HTTP transport started');
|
|
60
|
+
} catch (error) {
|
|
61
|
+
throw new MCPTransportError('Failed to start HTTP transport', { error });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async stop(): Promise<void> {
|
|
66
|
+
if (!this.running) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
this.logger.info('Stopping HTTP transport');
|
|
71
|
+
|
|
72
|
+
this.running = false;
|
|
73
|
+
|
|
74
|
+
// Close all connections
|
|
75
|
+
for (const conn of this.connections) {
|
|
76
|
+
try {
|
|
77
|
+
conn.close();
|
|
78
|
+
} catch {
|
|
79
|
+
// Ignore errors
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
this.connections.clear();
|
|
83
|
+
|
|
84
|
+
// Shutdown server
|
|
85
|
+
if (this.server) {
|
|
86
|
+
await this.server.shutdown();
|
|
87
|
+
this.server = undefined;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
this.logger.info('HTTP transport stopped');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
onRequest(handler: RequestHandler): void {
|
|
94
|
+
this.requestHandler = handler;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
onNotification(handler: NotificationHandler): void {
|
|
98
|
+
this.notificationHandler = handler;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async sendNotification(notification: MCPNotification): Promise<void> {
|
|
102
|
+
// Send notification to all connected WebSocket clients
|
|
103
|
+
const message = JSON.stringify(notification);
|
|
104
|
+
const promises: Promise<void>[] = [];
|
|
105
|
+
|
|
106
|
+
for (const ws of this.activeWebSockets) {
|
|
107
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
108
|
+
promises.push(
|
|
109
|
+
new Promise<void>((resolve, reject) => {
|
|
110
|
+
try {
|
|
111
|
+
ws.send(message);
|
|
112
|
+
resolve();
|
|
113
|
+
} catch (error) {
|
|
114
|
+
reject(error);
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
await Promise.all(promises);
|
|
123
|
+
this.notificationCount++;
|
|
124
|
+
|
|
125
|
+
this.logger.debug('Notification sent to WebSocket clients', {
|
|
126
|
+
method: notification.method,
|
|
127
|
+
clientCount: this.activeWebSockets.size,
|
|
128
|
+
});
|
|
129
|
+
} catch (error) {
|
|
130
|
+
this.logger.error('Failed to send notification to some clients', { error });
|
|
131
|
+
throw new MCPTransportError('Failed to send notification', { error });
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async getHealthStatus(): Promise<{
|
|
136
|
+
healthy: boolean;
|
|
137
|
+
error?: string;
|
|
138
|
+
metrics?: Record<string, number>;
|
|
139
|
+
}> {
|
|
140
|
+
return {
|
|
141
|
+
healthy: this.running,
|
|
142
|
+
metrics: {
|
|
143
|
+
messagesReceived: this.messageCount,
|
|
144
|
+
notificationsSent: this.notificationCount,
|
|
145
|
+
activeConnections: this.connections.size,
|
|
146
|
+
activeWebSockets: this.activeWebSockets.size,
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
private async handleRequest(request: Request): Promise<Response> {
|
|
152
|
+
const url = new URL(request.url);
|
|
153
|
+
const pathname = url.pathname;
|
|
154
|
+
|
|
155
|
+
// Handle CORS preflight requests
|
|
156
|
+
if (request.method === 'OPTIONS') {
|
|
157
|
+
return this.handleCORS(request);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Handle WebSocket upgrade for real-time notifications
|
|
161
|
+
if (request.headers.get('upgrade') === 'websocket' && pathname === '/ws') {
|
|
162
|
+
return this.handleWebSocketUpgrade(request);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Only accept POST requests to /rpc for JSON-RPC
|
|
166
|
+
if (request.method !== 'POST' || pathname !== '/rpc') {
|
|
167
|
+
return this.createErrorResponse(405, 'Method not allowed');
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Add CORS headers to all responses
|
|
171
|
+
const headers = this.getCORSHeaders();
|
|
172
|
+
headers.set('content-type', 'application/json');
|
|
173
|
+
|
|
174
|
+
// Check content type
|
|
175
|
+
const contentType = request.headers.get('content-type');
|
|
176
|
+
if (!contentType?.includes('application/json')) {
|
|
177
|
+
return this.createErrorResponse(400, 'Invalid content type', headers);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Check authorization if authentication is enabled
|
|
181
|
+
if (this.config?.auth?.enabled) {
|
|
182
|
+
const authResult = await this.validateAuth(request);
|
|
183
|
+
if (!authResult.valid) {
|
|
184
|
+
return this.createErrorResponse(401, authResult.error || 'Unauthorized', headers);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
// Parse request body
|
|
190
|
+
const body = await request.text();
|
|
191
|
+
|
|
192
|
+
let mcpMessage: any;
|
|
193
|
+
try {
|
|
194
|
+
mcpMessage = JSON.parse(body);
|
|
195
|
+
} catch {
|
|
196
|
+
return new Response(
|
|
197
|
+
JSON.stringify({
|
|
198
|
+
jsonrpc: '2.0',
|
|
199
|
+
id: null,
|
|
200
|
+
error: {
|
|
201
|
+
code: -32700,
|
|
202
|
+
message: 'Parse error',
|
|
203
|
+
},
|
|
204
|
+
}),
|
|
205
|
+
{ status: 400, headers },
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Validate JSON-RPC format
|
|
210
|
+
if (!mcpMessage.jsonrpc || mcpMessage.jsonrpc !== '2.0') {
|
|
211
|
+
return new Response(
|
|
212
|
+
JSON.stringify({
|
|
213
|
+
jsonrpc: '2.0',
|
|
214
|
+
id: mcpMessage.id || null,
|
|
215
|
+
error: {
|
|
216
|
+
code: -32600,
|
|
217
|
+
message: 'Invalid request - missing or invalid jsonrpc version',
|
|
218
|
+
},
|
|
219
|
+
}),
|
|
220
|
+
{ status: 400, headers },
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (!mcpMessage.method) {
|
|
225
|
+
return new Response(
|
|
226
|
+
JSON.stringify({
|
|
227
|
+
jsonrpc: '2.0',
|
|
228
|
+
id: mcpMessage.id || null,
|
|
229
|
+
error: {
|
|
230
|
+
code: -32600,
|
|
231
|
+
message: 'Invalid request - missing method',
|
|
232
|
+
},
|
|
233
|
+
}),
|
|
234
|
+
{ status: 400, headers },
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
this.messageCount++;
|
|
239
|
+
|
|
240
|
+
// Check if this is a notification (no id) or request
|
|
241
|
+
if (mcpMessage.id === undefined) {
|
|
242
|
+
// Handle notification
|
|
243
|
+
await this.handleNotificationMessage(mcpMessage as MCPNotification);
|
|
244
|
+
// Notifications don't get responses
|
|
245
|
+
return new Response('', { status: 204, headers });
|
|
246
|
+
} else {
|
|
247
|
+
// Handle request
|
|
248
|
+
const response = await this.handleRequestMessage(mcpMessage as MCPRequest);
|
|
249
|
+
return new Response(JSON.stringify(response), { status: 200, headers });
|
|
250
|
+
}
|
|
251
|
+
} catch (error) {
|
|
252
|
+
this.logger.error('Error handling HTTP request', error);
|
|
253
|
+
|
|
254
|
+
return new Response(
|
|
255
|
+
JSON.stringify({
|
|
256
|
+
jsonrpc: '2.0',
|
|
257
|
+
id: null,
|
|
258
|
+
error: {
|
|
259
|
+
code: -32603,
|
|
260
|
+
message: 'Internal error',
|
|
261
|
+
data: error instanceof Error ? error.message : String(error),
|
|
262
|
+
},
|
|
263
|
+
}),
|
|
264
|
+
{ status: 500, headers },
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
private handleCORS(request: Request): Response {
|
|
270
|
+
const headers = this.getCORSHeaders();
|
|
271
|
+
|
|
272
|
+
// Handle preflight request
|
|
273
|
+
const requestMethod = request.headers.get('access-control-request-method');
|
|
274
|
+
const requestHeaders = request.headers.get('access-control-request-headers');
|
|
275
|
+
|
|
276
|
+
if (requestMethod) {
|
|
277
|
+
headers.set('access-control-allow-methods', 'POST, OPTIONS');
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (requestHeaders) {
|
|
281
|
+
headers.set('access-control-allow-headers', requestHeaders);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return new Response('', { status: 204, headers });
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
private getCORSHeaders(): Headers {
|
|
288
|
+
const headers = new Headers();
|
|
289
|
+
|
|
290
|
+
if (this.config?.corsEnabled) {
|
|
291
|
+
const origins = this.config.corsOrigins || ['*'];
|
|
292
|
+
headers.set('access-control-allow-origin', origins.join(', '));
|
|
293
|
+
headers.set('access-control-allow-credentials', 'true');
|
|
294
|
+
headers.set('access-control-max-age', '86400'); // 24 hours
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return headers;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
private async handleWebSocketUpgrade(request: Request): Promise<Response> {
|
|
301
|
+
try {
|
|
302
|
+
// Check authentication for WebSocket connections
|
|
303
|
+
if (this.config?.auth?.enabled) {
|
|
304
|
+
const authResult = await this.validateAuth(request);
|
|
305
|
+
if (!authResult.valid) {
|
|
306
|
+
return this.createErrorResponse(401, authResult.error || 'Unauthorized');
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const { socket, response } = Deno.upgradeWebSocket(request);
|
|
311
|
+
|
|
312
|
+
socket.onopen = () => {
|
|
313
|
+
this.activeWebSockets.add(socket);
|
|
314
|
+
this.logger.info('WebSocket client connected', {
|
|
315
|
+
totalClients: this.activeWebSockets.size,
|
|
316
|
+
});
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
socket.onclose = () => {
|
|
320
|
+
this.activeWebSockets.delete(socket);
|
|
321
|
+
this.logger.info('WebSocket client disconnected', {
|
|
322
|
+
totalClients: this.activeWebSockets.size,
|
|
323
|
+
});
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
socket.onerror = (error) => {
|
|
327
|
+
this.logger.error('WebSocket error', error);
|
|
328
|
+
this.activeWebSockets.delete(socket);
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
socket.onmessage = async (event) => {
|
|
332
|
+
try {
|
|
333
|
+
const message = JSON.parse(event.data);
|
|
334
|
+
|
|
335
|
+
if (message.id === undefined) {
|
|
336
|
+
// Notification from client
|
|
337
|
+
await this.handleNotificationMessage(message as MCPNotification);
|
|
338
|
+
} else {
|
|
339
|
+
// Request from client
|
|
340
|
+
const response = await this.handleRequestMessage(message as MCPRequest);
|
|
341
|
+
socket.send(JSON.stringify(response));
|
|
342
|
+
}
|
|
343
|
+
} catch (error) {
|
|
344
|
+
this.logger.error('Error processing WebSocket message', error);
|
|
345
|
+
|
|
346
|
+
// Send error response if it was a request
|
|
347
|
+
try {
|
|
348
|
+
const parsed = JSON.parse(event.data);
|
|
349
|
+
if (parsed.id !== undefined) {
|
|
350
|
+
socket.send(JSON.stringify({
|
|
351
|
+
jsonrpc: '2.0',
|
|
352
|
+
id: parsed.id,
|
|
353
|
+
error: {
|
|
354
|
+
code: -32603,
|
|
355
|
+
message: 'Internal error',
|
|
356
|
+
},
|
|
357
|
+
}));
|
|
358
|
+
}
|
|
359
|
+
} catch {
|
|
360
|
+
// Ignore parse errors for error responses
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
return response;
|
|
366
|
+
} catch (error) {
|
|
367
|
+
this.logger.error('Error upgrading WebSocket connection', error);
|
|
368
|
+
return this.createErrorResponse(500, 'Failed to upgrade WebSocket connection');
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
private async handleRequestMessage(request: MCPRequest): Promise<MCPResponse> {
|
|
373
|
+
if (!this.requestHandler) {
|
|
374
|
+
return {
|
|
375
|
+
jsonrpc: '2.0',
|
|
376
|
+
id: request.id,
|
|
377
|
+
error: {
|
|
378
|
+
code: -32603,
|
|
379
|
+
message: 'No request handler registered',
|
|
380
|
+
},
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
try {
|
|
385
|
+
return await this.requestHandler(request);
|
|
386
|
+
} catch (error) {
|
|
387
|
+
this.logger.error('Request handler error', { request, error });
|
|
388
|
+
|
|
389
|
+
return {
|
|
390
|
+
jsonrpc: '2.0',
|
|
391
|
+
id: request.id,
|
|
392
|
+
error: {
|
|
393
|
+
code: -32603,
|
|
394
|
+
message: 'Internal error',
|
|
395
|
+
data: error instanceof Error ? error.message : String(error),
|
|
396
|
+
},
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
private async handleNotificationMessage(notification: MCPNotification): Promise<void> {
|
|
402
|
+
if (!this.notificationHandler) {
|
|
403
|
+
this.logger.warn('Received notification but no handler registered', {
|
|
404
|
+
method: notification.method,
|
|
405
|
+
});
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
try {
|
|
410
|
+
await this.notificationHandler(notification);
|
|
411
|
+
} catch (error) {
|
|
412
|
+
this.logger.error('Notification handler error', { notification, error });
|
|
413
|
+
// Notifications don't send error responses
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
private async validateAuth(request: Request): Promise<{ valid: boolean; error?: string }> {
|
|
418
|
+
const auth = request.headers.get('authorization');
|
|
419
|
+
|
|
420
|
+
if (!auth) {
|
|
421
|
+
return { valid: false, error: 'Authorization header required' };
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Extract token from Authorization header
|
|
425
|
+
const tokenMatch = auth.match(/^Bearer\s+(.+)$/i);
|
|
426
|
+
if (!tokenMatch) {
|
|
427
|
+
return { valid: false, error: 'Invalid authorization format - use Bearer token' };
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
const token = tokenMatch[1];
|
|
431
|
+
|
|
432
|
+
// Validate against configured tokens
|
|
433
|
+
if (this.config?.auth?.tokens && this.config.auth.tokens.length > 0) {
|
|
434
|
+
const isValid = this.config.auth.tokens.includes(token);
|
|
435
|
+
if (!isValid) {
|
|
436
|
+
return { valid: false, error: 'Invalid token' };
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return { valid: true };
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
private createErrorResponse(status: number, message: string, headers?: Headers): Response {
|
|
444
|
+
const responseHeaders = headers || this.getCORSHeaders();
|
|
445
|
+
responseHeaders.set('content-type', 'application/json');
|
|
446
|
+
|
|
447
|
+
return new Response(
|
|
448
|
+
JSON.stringify({
|
|
449
|
+
error: {
|
|
450
|
+
code: status,
|
|
451
|
+
message,
|
|
452
|
+
},
|
|
453
|
+
}),
|
|
454
|
+
{ status, headers: responseHeaders },
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standard I/O transport for MCP
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { ITransport, RequestHandler, NotificationHandler } from './base.ts';
|
|
6
|
+
import { MCPRequest, MCPResponse, MCPNotification } from '../../utils/types.ts';
|
|
7
|
+
import { ILogger } from '../../core/logger.ts';
|
|
8
|
+
import { MCPTransportError } from '../../utils/errors.ts';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Stdio transport implementation
|
|
12
|
+
*/
|
|
13
|
+
export class StdioTransport implements ITransport {
|
|
14
|
+
private requestHandler?: RequestHandler;
|
|
15
|
+
private notificationHandler?: NotificationHandler;
|
|
16
|
+
private decoder = new TextDecoder();
|
|
17
|
+
private encoder = new TextEncoder();
|
|
18
|
+
private buffer = '';
|
|
19
|
+
private messageCount = 0;
|
|
20
|
+
private notificationCount = 0;
|
|
21
|
+
private running = false;
|
|
22
|
+
private reader?: ReadableStreamDefaultReader<Uint8Array> | undefined;
|
|
23
|
+
|
|
24
|
+
constructor(private logger: ILogger) {}
|
|
25
|
+
|
|
26
|
+
async start(): Promise<void> {
|
|
27
|
+
if (this.running) {
|
|
28
|
+
throw new MCPTransportError('Transport already running');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
this.logger.info('Starting stdio transport');
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
// Start reading from stdin
|
|
35
|
+
this.reader = Deno.stdin.readable.getReader();
|
|
36
|
+
this.running = true;
|
|
37
|
+
|
|
38
|
+
// Start read loop
|
|
39
|
+
this.readLoop();
|
|
40
|
+
|
|
41
|
+
this.logger.info('Stdio transport started');
|
|
42
|
+
} catch (error) {
|
|
43
|
+
throw new MCPTransportError('Failed to start stdio transport', { error });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async stop(): Promise<void> {
|
|
48
|
+
if (!this.running) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
this.logger.info('Stopping stdio transport');
|
|
53
|
+
|
|
54
|
+
this.running = false;
|
|
55
|
+
|
|
56
|
+
if (this.reader) {
|
|
57
|
+
await this.reader.cancel();
|
|
58
|
+
this.reader = undefined;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
this.logger.info('Stdio transport stopped');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
onRequest(handler: RequestHandler): void {
|
|
65
|
+
this.requestHandler = handler;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
onNotification(handler: NotificationHandler): void {
|
|
69
|
+
this.notificationHandler = handler;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async sendNotification(notification: MCPNotification): Promise<void> {
|
|
73
|
+
try {
|
|
74
|
+
const json = JSON.stringify(notification);
|
|
75
|
+
const data = this.encoder.encode(json + '\n');
|
|
76
|
+
|
|
77
|
+
await Deno.stdout.write(data);
|
|
78
|
+
this.notificationCount++;
|
|
79
|
+
|
|
80
|
+
this.logger.debug('Notification sent', {
|
|
81
|
+
method: notification.method,
|
|
82
|
+
params: notification.params,
|
|
83
|
+
});
|
|
84
|
+
} catch (error) {
|
|
85
|
+
this.logger.error('Failed to send notification', { notification, error });
|
|
86
|
+
throw new MCPTransportError('Failed to send notification', { error });
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async getHealthStatus(): Promise<{
|
|
91
|
+
healthy: boolean;
|
|
92
|
+
error?: string;
|
|
93
|
+
metrics?: Record<string, number>;
|
|
94
|
+
}> {
|
|
95
|
+
return {
|
|
96
|
+
healthy: this.running,
|
|
97
|
+
metrics: {
|
|
98
|
+
messagesReceived: this.messageCount,
|
|
99
|
+
notificationsSent: this.notificationCount,
|
|
100
|
+
bufferSize: this.buffer.length,
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
private async readLoop(): Promise<void> {
|
|
106
|
+
while (this.running && this.reader) {
|
|
107
|
+
try {
|
|
108
|
+
const { done, value } = await this.reader.read();
|
|
109
|
+
|
|
110
|
+
if (done) {
|
|
111
|
+
this.logger.info('Stdin closed');
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Add to buffer
|
|
116
|
+
this.buffer += this.decoder.decode(value, { stream: true });
|
|
117
|
+
|
|
118
|
+
// Process complete messages
|
|
119
|
+
await this.processBuffer();
|
|
120
|
+
} catch (error) {
|
|
121
|
+
if (this.running) {
|
|
122
|
+
this.logger.error('Error reading from stdin', error);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private async processBuffer(): Promise<void> {
|
|
129
|
+
let newlineIndex: number;
|
|
130
|
+
|
|
131
|
+
while ((newlineIndex = this.buffer.indexOf('\n')) !== -1) {
|
|
132
|
+
const line = this.buffer.slice(0, newlineIndex).trim();
|
|
133
|
+
this.buffer = this.buffer.slice(newlineIndex + 1);
|
|
134
|
+
|
|
135
|
+
if (line.length === 0) {
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
await this.processMessage(line);
|
|
141
|
+
} catch (error) {
|
|
142
|
+
this.logger.error('Error processing message', { line, error });
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
private async processMessage(line: string): Promise<void> {
|
|
148
|
+
let message: any;
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
message = JSON.parse(line);
|
|
152
|
+
|
|
153
|
+
if (!message.jsonrpc || message.jsonrpc !== '2.0') {
|
|
154
|
+
throw new Error('Invalid JSON-RPC version');
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (!message.method) {
|
|
158
|
+
throw new Error('Missing method');
|
|
159
|
+
}
|
|
160
|
+
} catch (error) {
|
|
161
|
+
this.logger.error('Failed to parse message', { line, error });
|
|
162
|
+
|
|
163
|
+
// Send error response if we can extract an ID
|
|
164
|
+
let id = 'unknown';
|
|
165
|
+
try {
|
|
166
|
+
const parsed = JSON.parse(line);
|
|
167
|
+
if (parsed.id !== undefined) {
|
|
168
|
+
id = parsed.id;
|
|
169
|
+
}
|
|
170
|
+
} catch {
|
|
171
|
+
// Ignore parse error for ID extraction
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
await this.sendResponse({
|
|
175
|
+
jsonrpc: '2.0',
|
|
176
|
+
id,
|
|
177
|
+
error: {
|
|
178
|
+
code: -32700,
|
|
179
|
+
message: 'Parse error',
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
this.messageCount++;
|
|
186
|
+
|
|
187
|
+
// Check if this is a notification (no id field) or a request
|
|
188
|
+
if (message.id === undefined) {
|
|
189
|
+
// This is a notification
|
|
190
|
+
await this.handleNotification(message as MCPNotification);
|
|
191
|
+
} else {
|
|
192
|
+
// This is a request
|
|
193
|
+
await this.handleRequest(message as MCPRequest);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
private async handleRequest(request: MCPRequest): Promise<void> {
|
|
198
|
+
if (!this.requestHandler) {
|
|
199
|
+
await this.sendResponse({
|
|
200
|
+
jsonrpc: '2.0',
|
|
201
|
+
id: request.id,
|
|
202
|
+
error: {
|
|
203
|
+
code: -32603,
|
|
204
|
+
message: 'No request handler registered',
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
try {
|
|
211
|
+
const response = await this.requestHandler(request);
|
|
212
|
+
await this.sendResponse(response);
|
|
213
|
+
} catch (error) {
|
|
214
|
+
this.logger.error('Request handler error', { request, error });
|
|
215
|
+
|
|
216
|
+
await this.sendResponse({
|
|
217
|
+
jsonrpc: '2.0',
|
|
218
|
+
id: request.id,
|
|
219
|
+
error: {
|
|
220
|
+
code: -32603,
|
|
221
|
+
message: 'Internal error',
|
|
222
|
+
data: error instanceof Error ? error.message : String(error),
|
|
223
|
+
},
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
private async handleNotification(notification: MCPNotification): Promise<void> {
|
|
229
|
+
if (!this.notificationHandler) {
|
|
230
|
+
this.logger.warn('Received notification but no handler registered', {
|
|
231
|
+
method: notification.method,
|
|
232
|
+
});
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
try {
|
|
237
|
+
await this.notificationHandler(notification);
|
|
238
|
+
} catch (error) {
|
|
239
|
+
this.logger.error('Notification handler error', { notification, error });
|
|
240
|
+
// Notifications don't send error responses
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
private async sendResponse(response: MCPResponse): Promise<void> {
|
|
245
|
+
try {
|
|
246
|
+
const json = JSON.stringify(response);
|
|
247
|
+
const data = this.encoder.encode(json + '\n');
|
|
248
|
+
|
|
249
|
+
await Deno.stdout.write(data);
|
|
250
|
+
} catch (error) {
|
|
251
|
+
this.logger.error('Failed to send response', { response, error });
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|