@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,294 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* V3 MCP Transport Factory
|
|
3
|
+
*
|
|
4
|
+
* Central factory for creating transport instances:
|
|
5
|
+
* - Unified transport creation API
|
|
6
|
+
* - Transport type validation
|
|
7
|
+
* - Configuration defaults
|
|
8
|
+
* - Multi-transport support
|
|
9
|
+
*
|
|
10
|
+
* Supported transports:
|
|
11
|
+
* - stdio: Standard I/O (default for CLI)
|
|
12
|
+
* - http: HTTP/REST with WebSocket upgrade
|
|
13
|
+
* - websocket: Standalone WebSocket
|
|
14
|
+
* - in-process: Direct function calls (fastest)
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
ITransport,
|
|
19
|
+
TransportType,
|
|
20
|
+
TransportHealthStatus,
|
|
21
|
+
ILogger,
|
|
22
|
+
} from '../types.js';
|
|
23
|
+
import { StdioTransport, StdioTransportConfig, createStdioTransport } from './stdio.js';
|
|
24
|
+
import { HttpTransport, HttpTransportConfig, createHttpTransport } from './http.js';
|
|
25
|
+
import { WebSocketTransport, WebSocketTransportConfig, createWebSocketTransport } from './websocket.js';
|
|
26
|
+
|
|
27
|
+
// Re-export transport classes (values)
|
|
28
|
+
export { StdioTransport } from './stdio.js';
|
|
29
|
+
export { HttpTransport } from './http.js';
|
|
30
|
+
export { WebSocketTransport } from './websocket.js';
|
|
31
|
+
|
|
32
|
+
// Re-export transport config types
|
|
33
|
+
export type { StdioTransportConfig } from './stdio.js';
|
|
34
|
+
export type { HttpTransportConfig } from './http.js';
|
|
35
|
+
export type { WebSocketTransportConfig } from './websocket.js';
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Transport configuration union
|
|
39
|
+
*/
|
|
40
|
+
export type TransportConfig =
|
|
41
|
+
| { type: 'stdio' } & StdioTransportConfig
|
|
42
|
+
| { type: 'http' } & HttpTransportConfig
|
|
43
|
+
| { type: 'websocket' } & WebSocketTransportConfig
|
|
44
|
+
| { type: 'in-process' };
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Create a transport instance based on type
|
|
48
|
+
*/
|
|
49
|
+
export function createTransport(
|
|
50
|
+
type: TransportType,
|
|
51
|
+
logger: ILogger,
|
|
52
|
+
config?: Partial<TransportConfig>
|
|
53
|
+
): ITransport {
|
|
54
|
+
switch (type) {
|
|
55
|
+
case 'stdio':
|
|
56
|
+
return createStdioTransport(logger, config as StdioTransportConfig);
|
|
57
|
+
|
|
58
|
+
case 'http':
|
|
59
|
+
if (!config || !('host' in config) || !('port' in config)) {
|
|
60
|
+
throw new Error('HTTP transport requires host and port configuration');
|
|
61
|
+
}
|
|
62
|
+
return createHttpTransport(logger, {
|
|
63
|
+
host: config.host as string,
|
|
64
|
+
port: config.port as number,
|
|
65
|
+
...config,
|
|
66
|
+
} as HttpTransportConfig);
|
|
67
|
+
|
|
68
|
+
case 'websocket':
|
|
69
|
+
if (!config || !('host' in config) || !('port' in config)) {
|
|
70
|
+
throw new Error('WebSocket transport requires host and port configuration');
|
|
71
|
+
}
|
|
72
|
+
return createWebSocketTransport(logger, {
|
|
73
|
+
host: config.host as string,
|
|
74
|
+
port: config.port as number,
|
|
75
|
+
...config,
|
|
76
|
+
} as WebSocketTransportConfig);
|
|
77
|
+
|
|
78
|
+
case 'in-process':
|
|
79
|
+
// In-process transport is handled directly by the server
|
|
80
|
+
// Return a no-op transport wrapper
|
|
81
|
+
return createInProcessTransport(logger);
|
|
82
|
+
|
|
83
|
+
default:
|
|
84
|
+
throw new Error(`Unknown transport type: ${type}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* In-process transport (no-op wrapper)
|
|
90
|
+
*
|
|
91
|
+
* Used when tools are executed directly without network transport
|
|
92
|
+
*/
|
|
93
|
+
class InProcessTransport implements ITransport {
|
|
94
|
+
public readonly type: TransportType = 'in-process';
|
|
95
|
+
|
|
96
|
+
constructor(private readonly logger: ILogger) {}
|
|
97
|
+
|
|
98
|
+
async start(): Promise<void> {
|
|
99
|
+
this.logger.debug('In-process transport started');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async stop(): Promise<void> {
|
|
103
|
+
this.logger.debug('In-process transport stopped');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
onRequest(): void {
|
|
107
|
+
// No-op - requests are handled directly
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
onNotification(): void {
|
|
111
|
+
// No-op - notifications are handled directly
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async getHealthStatus(): Promise<TransportHealthStatus> {
|
|
115
|
+
return {
|
|
116
|
+
healthy: true,
|
|
117
|
+
metrics: {
|
|
118
|
+
latency: 0,
|
|
119
|
+
connections: 1,
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Create in-process transport
|
|
127
|
+
*/
|
|
128
|
+
export function createInProcessTransport(logger: ILogger): ITransport {
|
|
129
|
+
return new InProcessTransport(logger);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Transport manager for multi-transport scenarios
|
|
134
|
+
*/
|
|
135
|
+
export class TransportManager {
|
|
136
|
+
private transports: Map<string, ITransport> = new Map();
|
|
137
|
+
private running = false;
|
|
138
|
+
|
|
139
|
+
constructor(private readonly logger: ILogger) {}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Add a transport
|
|
143
|
+
*/
|
|
144
|
+
addTransport(name: string, transport: ITransport): void {
|
|
145
|
+
if (this.transports.has(name)) {
|
|
146
|
+
throw new Error(`Transport "${name}" already exists`);
|
|
147
|
+
}
|
|
148
|
+
this.transports.set(name, transport);
|
|
149
|
+
this.logger.debug('Transport added', { name, type: transport.type });
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Remove a transport
|
|
154
|
+
*/
|
|
155
|
+
async removeTransport(name: string): Promise<boolean> {
|
|
156
|
+
const transport = this.transports.get(name);
|
|
157
|
+
if (!transport) {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
await transport.stop();
|
|
162
|
+
this.transports.delete(name);
|
|
163
|
+
this.logger.debug('Transport removed', { name });
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Get a transport by name
|
|
169
|
+
*/
|
|
170
|
+
getTransport(name: string): ITransport | undefined {
|
|
171
|
+
return this.transports.get(name);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Get all transport names
|
|
176
|
+
*/
|
|
177
|
+
getTransportNames(): string[] {
|
|
178
|
+
return Array.from(this.transports.keys());
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Start all transports
|
|
183
|
+
*/
|
|
184
|
+
async startAll(): Promise<void> {
|
|
185
|
+
if (this.running) {
|
|
186
|
+
throw new Error('TransportManager already running');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
this.logger.info('Starting all transports', { count: this.transports.size });
|
|
190
|
+
|
|
191
|
+
const startPromises = Array.from(this.transports.entries()).map(
|
|
192
|
+
async ([name, transport]) => {
|
|
193
|
+
try {
|
|
194
|
+
await transport.start();
|
|
195
|
+
this.logger.info('Transport started', { name, type: transport.type });
|
|
196
|
+
} catch (error) {
|
|
197
|
+
this.logger.error('Failed to start transport', { name, error });
|
|
198
|
+
throw error;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
await Promise.all(startPromises);
|
|
204
|
+
this.running = true;
|
|
205
|
+
this.logger.info('All transports started');
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Stop all transports
|
|
210
|
+
*/
|
|
211
|
+
async stopAll(): Promise<void> {
|
|
212
|
+
if (!this.running) {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
this.logger.info('Stopping all transports');
|
|
217
|
+
|
|
218
|
+
const stopPromises = Array.from(this.transports.entries()).map(
|
|
219
|
+
async ([name, transport]) => {
|
|
220
|
+
try {
|
|
221
|
+
await transport.stop();
|
|
222
|
+
this.logger.info('Transport stopped', { name });
|
|
223
|
+
} catch (error) {
|
|
224
|
+
this.logger.error('Error stopping transport', { name, error });
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
await Promise.all(stopPromises);
|
|
230
|
+
this.running = false;
|
|
231
|
+
this.logger.info('All transports stopped');
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Get health status of all transports
|
|
236
|
+
*/
|
|
237
|
+
async getHealthStatus(): Promise<Record<string, { healthy: boolean; error?: string }>> {
|
|
238
|
+
const results: Record<string, { healthy: boolean; error?: string }> = {};
|
|
239
|
+
|
|
240
|
+
for (const [name, transport] of this.transports) {
|
|
241
|
+
try {
|
|
242
|
+
const status = await transport.getHealthStatus();
|
|
243
|
+
results[name] = { healthy: status.healthy, error: status.error };
|
|
244
|
+
} catch (error) {
|
|
245
|
+
results[name] = {
|
|
246
|
+
healthy: false,
|
|
247
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return results;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Check if any transport is running
|
|
257
|
+
*/
|
|
258
|
+
isRunning(): boolean {
|
|
259
|
+
return this.running;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Create a transport manager
|
|
265
|
+
*/
|
|
266
|
+
export function createTransportManager(logger: ILogger): TransportManager {
|
|
267
|
+
return new TransportManager(logger);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Default transport configurations
|
|
272
|
+
*/
|
|
273
|
+
export const DEFAULT_TRANSPORT_CONFIGS = {
|
|
274
|
+
stdio: {} as StdioTransportConfig,
|
|
275
|
+
|
|
276
|
+
http: {
|
|
277
|
+
host: 'localhost',
|
|
278
|
+
port: 3000,
|
|
279
|
+
corsEnabled: true,
|
|
280
|
+
corsOrigins: ['*'],
|
|
281
|
+
maxRequestSize: '10mb',
|
|
282
|
+
requestTimeout: 30000,
|
|
283
|
+
} as HttpTransportConfig,
|
|
284
|
+
|
|
285
|
+
websocket: {
|
|
286
|
+
host: 'localhost',
|
|
287
|
+
port: 3001,
|
|
288
|
+
path: '/ws',
|
|
289
|
+
maxConnections: 100,
|
|
290
|
+
heartbeatInterval: 30000,
|
|
291
|
+
heartbeatTimeout: 10000,
|
|
292
|
+
maxMessageSize: 10 * 1024 * 1024,
|
|
293
|
+
} as WebSocketTransportConfig,
|
|
294
|
+
} as const;
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* V3 MCP Stdio Transport
|
|
3
|
+
*
|
|
4
|
+
* Standard I/O transport for MCP communication:
|
|
5
|
+
* - Optimized JSON parsing with streaming
|
|
6
|
+
* - Buffer management for large messages
|
|
7
|
+
* - Graceful shutdown handling
|
|
8
|
+
*
|
|
9
|
+
* Performance Targets:
|
|
10
|
+
* - Message parsing: <5ms
|
|
11
|
+
* - Response sending: <2ms
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { EventEmitter } from 'events';
|
|
15
|
+
import * as readline from 'readline';
|
|
16
|
+
import {
|
|
17
|
+
ITransport,
|
|
18
|
+
TransportType,
|
|
19
|
+
MCPRequest,
|
|
20
|
+
MCPResponse,
|
|
21
|
+
MCPNotification,
|
|
22
|
+
RequestHandler,
|
|
23
|
+
NotificationHandler,
|
|
24
|
+
TransportHealthStatus,
|
|
25
|
+
ILogger,
|
|
26
|
+
} from '../types.js';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Stdio Transport Configuration
|
|
30
|
+
*/
|
|
31
|
+
export interface StdioTransportConfig {
|
|
32
|
+
inputStream?: NodeJS.ReadableStream;
|
|
33
|
+
outputStream?: NodeJS.WritableStream;
|
|
34
|
+
maxMessageSize?: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Stdio Transport Implementation
|
|
39
|
+
*
|
|
40
|
+
* Uses readline for efficient line-by-line processing of JSON-RPC messages
|
|
41
|
+
*/
|
|
42
|
+
export class StdioTransport extends EventEmitter implements ITransport {
|
|
43
|
+
public readonly type: TransportType = 'stdio';
|
|
44
|
+
|
|
45
|
+
private requestHandler?: RequestHandler;
|
|
46
|
+
private notificationHandler?: NotificationHandler;
|
|
47
|
+
private rl?: readline.Interface;
|
|
48
|
+
private running = false;
|
|
49
|
+
private messageBuffer = '';
|
|
50
|
+
|
|
51
|
+
// Statistics
|
|
52
|
+
private messagesReceived = 0;
|
|
53
|
+
private messagesSent = 0;
|
|
54
|
+
private errors = 0;
|
|
55
|
+
|
|
56
|
+
private readonly inputStream: NodeJS.ReadableStream;
|
|
57
|
+
private readonly outputStream: NodeJS.WritableStream;
|
|
58
|
+
private readonly maxMessageSize: number;
|
|
59
|
+
|
|
60
|
+
constructor(
|
|
61
|
+
private readonly logger: ILogger,
|
|
62
|
+
config: StdioTransportConfig = {}
|
|
63
|
+
) {
|
|
64
|
+
super();
|
|
65
|
+
this.inputStream = config.inputStream || process.stdin;
|
|
66
|
+
this.outputStream = config.outputStream || process.stdout;
|
|
67
|
+
this.maxMessageSize = config.maxMessageSize || 10 * 1024 * 1024; // 10MB default
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Start the transport
|
|
72
|
+
*/
|
|
73
|
+
async start(): Promise<void> {
|
|
74
|
+
if (this.running) {
|
|
75
|
+
throw new Error('Stdio transport already running');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
this.logger.info('Starting stdio transport');
|
|
79
|
+
|
|
80
|
+
// Create readline interface for efficient line processing
|
|
81
|
+
this.rl = readline.createInterface({
|
|
82
|
+
input: this.inputStream,
|
|
83
|
+
crlfDelay: Infinity,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// Handle incoming lines
|
|
87
|
+
this.rl.on('line', (line) => {
|
|
88
|
+
this.handleLine(line);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Handle close
|
|
92
|
+
this.rl.on('close', () => {
|
|
93
|
+
this.handleClose();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// Handle errors on input stream
|
|
97
|
+
this.inputStream.on('error', (error) => {
|
|
98
|
+
this.handleError(error);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
this.running = true;
|
|
102
|
+
this.logger.info('Stdio transport started');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Stop the transport
|
|
107
|
+
*/
|
|
108
|
+
async stop(): Promise<void> {
|
|
109
|
+
if (!this.running) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
this.logger.info('Stopping stdio transport');
|
|
114
|
+
this.running = false;
|
|
115
|
+
|
|
116
|
+
if (this.rl) {
|
|
117
|
+
this.rl.close();
|
|
118
|
+
this.rl = undefined;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
this.logger.info('Stdio transport stopped');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Register request handler
|
|
126
|
+
*/
|
|
127
|
+
onRequest(handler: RequestHandler): void {
|
|
128
|
+
this.requestHandler = handler;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Register notification handler
|
|
133
|
+
*/
|
|
134
|
+
onNotification(handler: NotificationHandler): void {
|
|
135
|
+
this.notificationHandler = handler;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Get health status
|
|
140
|
+
*/
|
|
141
|
+
async getHealthStatus(): Promise<TransportHealthStatus> {
|
|
142
|
+
return {
|
|
143
|
+
healthy: this.running,
|
|
144
|
+
metrics: {
|
|
145
|
+
messagesReceived: this.messagesReceived,
|
|
146
|
+
messagesSent: this.messagesSent,
|
|
147
|
+
errors: this.errors,
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Handle incoming line
|
|
154
|
+
*/
|
|
155
|
+
private async handleLine(line: string): Promise<void> {
|
|
156
|
+
if (!line.trim()) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Check message size
|
|
161
|
+
if (line.length > this.maxMessageSize) {
|
|
162
|
+
this.logger.error('Message exceeds maximum size', {
|
|
163
|
+
size: line.length,
|
|
164
|
+
max: this.maxMessageSize,
|
|
165
|
+
});
|
|
166
|
+
this.errors++;
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
try {
|
|
171
|
+
const message = JSON.parse(line);
|
|
172
|
+
this.messagesReceived++;
|
|
173
|
+
|
|
174
|
+
// Validate JSON-RPC format
|
|
175
|
+
if (message.jsonrpc !== '2.0') {
|
|
176
|
+
this.logger.warn('Invalid JSON-RPC version', { received: message.jsonrpc });
|
|
177
|
+
await this.sendError(message.id, -32600, 'Invalid JSON-RPC version');
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (!message.method) {
|
|
182
|
+
this.logger.warn('Missing method in request');
|
|
183
|
+
await this.sendError(message.id, -32600, 'Missing method');
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Determine if this is a request or notification
|
|
188
|
+
if (message.id !== undefined) {
|
|
189
|
+
// Request - needs response
|
|
190
|
+
await this.handleRequest(message as MCPRequest);
|
|
191
|
+
} else {
|
|
192
|
+
// Notification - no response needed
|
|
193
|
+
await this.handleNotification(message as MCPNotification);
|
|
194
|
+
}
|
|
195
|
+
} catch (error) {
|
|
196
|
+
this.errors++;
|
|
197
|
+
this.logger.error('Failed to parse message', { error, line: line.substring(0, 100) });
|
|
198
|
+
await this.sendError(null, -32700, 'Parse error');
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Handle MCP request
|
|
204
|
+
*/
|
|
205
|
+
private async handleRequest(request: MCPRequest): Promise<void> {
|
|
206
|
+
if (!this.requestHandler) {
|
|
207
|
+
this.logger.warn('No request handler registered');
|
|
208
|
+
await this.sendError(request.id, -32603, 'No request handler');
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
try {
|
|
213
|
+
const startTime = performance.now();
|
|
214
|
+
const response = await this.requestHandler(request);
|
|
215
|
+
const duration = performance.now() - startTime;
|
|
216
|
+
|
|
217
|
+
this.logger.debug('Request processed', {
|
|
218
|
+
method: request.method,
|
|
219
|
+
duration: `${duration.toFixed(2)}ms`,
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
await this.sendResponse(response);
|
|
223
|
+
} catch (error) {
|
|
224
|
+
this.logger.error('Request handler error', { method: request.method, error });
|
|
225
|
+
await this.sendError(
|
|
226
|
+
request.id,
|
|
227
|
+
-32603,
|
|
228
|
+
error instanceof Error ? error.message : 'Internal error'
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Handle MCP notification
|
|
235
|
+
*/
|
|
236
|
+
private async handleNotification(notification: MCPNotification): Promise<void> {
|
|
237
|
+
if (!this.notificationHandler) {
|
|
238
|
+
this.logger.debug('Notification received but no handler', { method: notification.method });
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
try {
|
|
243
|
+
await this.notificationHandler(notification);
|
|
244
|
+
} catch (error) {
|
|
245
|
+
this.logger.error('Notification handler error', { method: notification.method, error });
|
|
246
|
+
// Notifications don't send error responses
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Send response to stdout
|
|
252
|
+
*/
|
|
253
|
+
private async sendResponse(response: MCPResponse): Promise<void> {
|
|
254
|
+
const json = JSON.stringify(response);
|
|
255
|
+
await this.write(json);
|
|
256
|
+
this.messagesSent++;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Send error response
|
|
261
|
+
*/
|
|
262
|
+
private async sendError(id: string | number | null, code: number, message: string): Promise<void> {
|
|
263
|
+
const response: MCPResponse = {
|
|
264
|
+
jsonrpc: '2.0',
|
|
265
|
+
id,
|
|
266
|
+
error: { code, message },
|
|
267
|
+
};
|
|
268
|
+
await this.sendResponse(response);
|
|
269
|
+
this.errors++;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Send notification to stdout
|
|
274
|
+
*/
|
|
275
|
+
async sendNotification(notification: MCPNotification): Promise<void> {
|
|
276
|
+
const json = JSON.stringify(notification);
|
|
277
|
+
await this.write(json);
|
|
278
|
+
this.messagesSent++;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Write to output stream
|
|
283
|
+
*/
|
|
284
|
+
private write(data: string): Promise<void> {
|
|
285
|
+
return new Promise((resolve, reject) => {
|
|
286
|
+
this.outputStream.write(data + '\n', (error) => {
|
|
287
|
+
if (error) {
|
|
288
|
+
this.errors++;
|
|
289
|
+
reject(error);
|
|
290
|
+
} else {
|
|
291
|
+
resolve();
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Handle stream close
|
|
299
|
+
*/
|
|
300
|
+
private handleClose(): void {
|
|
301
|
+
this.logger.info('Stdio stream closed');
|
|
302
|
+
this.running = false;
|
|
303
|
+
this.emit('close');
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Handle stream error
|
|
308
|
+
*/
|
|
309
|
+
private handleError(error: Error): void {
|
|
310
|
+
this.logger.error('Stdio stream error', error);
|
|
311
|
+
this.errors++;
|
|
312
|
+
this.emit('error', error);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Create stdio transport
|
|
318
|
+
*/
|
|
319
|
+
export function createStdioTransport(
|
|
320
|
+
logger: ILogger,
|
|
321
|
+
config: StdioTransportConfig = {}
|
|
322
|
+
): StdioTransport {
|
|
323
|
+
return new StdioTransport(logger, config);
|
|
324
|
+
}
|