UNPKG

@cortexguardai/mcp

Version:

A Node.js-based MCP adapter for seamless integration with AI development environments.

167 lines 5.72 kB
import { EventEmitter } from 'events'; const CONTENT_LENGTH_HEADER = 'Content-Length:'; const HEADER_SEPARATOR = '\r\n\r\n'; const LINE_SEPARATOR = '\r\n'; export class StdioTransport extends EventEmitter { inputBuffer = ''; outputStream; inputStream; isShuttingDown = false; constructor() { super(); this.inputStream = process.stdin; this.outputStream = process.stdout; this.setupInputHandling(); } setupInputHandling() { // Set stdin to raw mode for binary data handling this.inputStream.setEncoding('utf8'); this.inputStream.on('data', (chunk) => { if (this.isShuttingDown) return; this.inputBuffer += chunk; this.processBuffer(); }); this.inputStream.on('end', () => { this.emit('disconnect'); }); this.inputStream.on('error', (error) => { this.emit('error', error); }); } processBuffer() { while (true) { const message = this.extractMessage(); if (!message) break; try { const parsed = JSON.parse(message.content); this.emit('message', parsed); } catch (error) { this.emit('error', new Error(`Invalid JSON in message: ${error}`)); } } } extractMessage() { // Look for Content-Length header const headerEndIndex = this.inputBuffer.indexOf(HEADER_SEPARATOR); if (headerEndIndex === -1) { return null; // No complete header yet } const headerSection = this.inputBuffer.substring(0, headerEndIndex); const contentStartIndex = headerEndIndex + HEADER_SEPARATOR.length; // Parse Content-Length const contentLengthMatch = headerSection.match(new RegExp(`${CONTENT_LENGTH_HEADER}\\s*(\\d+)`, 'i')); if (!contentLengthMatch) { // Invalid header format, skip this message this.inputBuffer = this.inputBuffer.substring(contentStartIndex); return null; } const contentLength = parseInt(contentLengthMatch[1], 10); if (isNaN(contentLength) || contentLength < 0) { // Invalid content length this.inputBuffer = this.inputBuffer.substring(contentStartIndex); return null; } // Check if we have the complete message if (this.inputBuffer.length < contentStartIndex + contentLength) { return null; // Incomplete message } // Extract the message content const content = this.inputBuffer.substring(contentStartIndex, contentStartIndex + contentLength); // Remove processed message from buffer this.inputBuffer = this.inputBuffer.substring(contentStartIndex + contentLength); return { contentLength, content }; } sendMessage(message) { if (this.isShuttingDown) return; try { const content = JSON.stringify(message); const contentLength = Buffer.byteLength(content, 'utf8'); const frame = `${CONTENT_LENGTH_HEADER} ${contentLength}${HEADER_SEPARATOR}${content}`; this.outputStream.write(frame); } catch (error) { this.emit('error', new Error(`Failed to send message: ${error}`)); } } sendError(id, code, message, data) { const errorResponse = { jsonrpc: '2.0', id, error: { code, message, data } }; this.sendMessage(errorResponse); } sendResult(id, result) { const response = { jsonrpc: '2.0', id, result }; this.sendMessage(response); } sendNotification(method, params) { const notification = { jsonrpc: '2.0', method, params }; try { const content = JSON.stringify(notification); const contentLength = Buffer.byteLength(content, 'utf8'); const frame = `${CONTENT_LENGTH_HEADER} ${contentLength}${HEADER_SEPARATOR}${content}`; this.outputStream.write(frame); } catch (error) { this.emit('error', new Error(`Failed to send notification: ${error}`)); } } isValidJsonRpcRequest(obj) { return (obj && typeof obj === 'object' && obj.jsonrpc === '2.0' && (typeof obj.id === 'string' || typeof obj.id === 'number') && typeof obj.method === 'string'); } start() { // Transport is already initialized in constructor // This method is required by the MCP server interface this.emit('ready'); } shutdown() { this.isShuttingDown = true; // Close streams gracefully if (this.inputStream && !this.inputStream.destroyed) { this.inputStream.destroy(); } if (this.outputStream && !this.outputStream.destroyed) { this.outputStream.end(); } this.emit('shutdown'); } // Utility method for debugging getBufferInfo() { return { bufferLength: this.inputBuffer.length, bufferPreview: this.inputBuffer.substring(0, 100) + (this.inputBuffer.length > 100 ? '...' : '') }; } // Alias methods for compatibility with rpc-handler async send(message) { this.sendMessage(message); } async close() { this.shutdown(); } } //# sourceMappingURL=stdio-transport.js.map