@cortexguardai/mcp
Version:
A Node.js-based MCP adapter for seamless integration with AI development environments.
167 lines • 5.72 kB
JavaScript
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