UNPKG

@cortexguardai/mcp

Version:

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

228 lines 8.05 kB
import { JsonRpcErrorCode } from './types.js'; export class RpcHandler { transport; dispatcher; pendingRequests = new Map(); isShuttingDown = false; constructor(transport, dispatcher) { this.transport = transport; this.dispatcher = dispatcher; // Handle incoming messages this.transport.on('message', this.handleMessage.bind(this)); this.transport.on('error', this.handleTransportError.bind(this)); // Handle graceful shutdown process.on('SIGINT', this.shutdown.bind(this)); process.on('SIGTERM', this.shutdown.bind(this)); } async start() { await this.transport.start(); } async handleMessage(message) { if (this.isShuttingDown) { // Reject new requests during shutdown if (this.isRequest(message)) { const errorResponse = { jsonrpc: '2.0', id: message.id, error: { code: JsonRpcErrorCode.SERVER_ERROR, message: 'Server is shutting down', data: { shutting_down: true } } }; await this.transport.send(errorResponse); } return; } try { if (this.isRequest(message)) { await this.handleRequest(message); } else if (this.isNotification(message)) { await this.handleNotification(message); } else { // Invalid message format const errorResponse = { jsonrpc: '2.0', id: 'unknown', error: { code: JsonRpcErrorCode.INVALID_REQUEST, message: 'Invalid JSON-RPC message format', data: { received: message } } }; await this.transport.send(errorResponse); } } catch (error) { console.error('Error handling message:', error); // Send error response if we can identify the request if (this.isRequest(message)) { const errorResponse = { jsonrpc: '2.0', id: message.id, error: { code: JsonRpcErrorCode.INTERNAL_ERROR, message: 'Internal server error', data: { error: String(error) } } }; await this.transport.send(errorResponse); } } } async handleRequest(request) { const requestId = request.id; // Set timeout for request processing const timeout = setTimeout(() => { this.pendingRequests.delete(requestId); const timeoutResponse = { jsonrpc: '2.0', id: requestId, error: { code: JsonRpcErrorCode.SERVER_ERROR, message: 'Request timeout', data: { method: request.method, timeout_ms: 30000 } } }; this.transport.send(timeoutResponse).catch(console.error); }, 30000); // 30 second timeout this.pendingRequests.set(requestId, timeout); try { // Validate request format if (!request.method || typeof request.method !== 'string') { throw { code: JsonRpcErrorCode.INVALID_REQUEST, message: 'Missing or invalid method', data: { received: request } }; } // Dispatch to method handler const result = await this.dispatcher.handleRequest({ jsonrpc: '2.0', id: request.id, method: request.method, params: request.params || {} }); // Clear timeout and send success response clearTimeout(timeout); this.pendingRequests.delete(requestId); const response = { jsonrpc: '2.0', id: requestId, result }; await this.transport.send(response); } catch (error) { // Clear timeout clearTimeout(timeout); this.pendingRequests.delete(requestId); // Map error to JSON-RPC error format const jsonRpcError = this.mapToJsonRpcError(error); const errorResponse = { jsonrpc: '2.0', id: requestId, error: jsonRpcError }; await this.transport.send(errorResponse); } } async handleNotification(notification) { try { // Notifications don't expect a response await this.dispatcher.handleRequest({ jsonrpc: '2.0', id: 'notification', method: notification.method, params: notification.params || {} }); } catch (error) { // Log notification errors but don't send response console.error(`Error handling notification ${notification.method}:`, error); } } handleTransportError(error) { console.error('Transport error:', error); // Attempt graceful shutdown on transport errors this.shutdown().catch(console.error); } async shutdown() { if (this.isShuttingDown) { return; } console.error('Shutting down MCP adapter...'); this.isShuttingDown = true; // Send error responses for pending requests const shutdownError = { code: JsonRpcErrorCode.SERVER_ERROR, message: 'Server shutting down', data: { shutting_down: true } }; for (const [requestId, timeout] of this.pendingRequests) { clearTimeout(timeout); const errorResponse = { jsonrpc: '2.0', id: requestId, error: shutdownError }; try { await this.transport.send(errorResponse); } catch (error) { console.error('Error sending shutdown response:', error); } } this.pendingRequests.clear(); // Close transport await this.transport.close(); // Exit process process.exit(0); } isRequest(message) { return (message && message.jsonrpc === '2.0' && typeof message.method === 'string' && (message.id !== undefined)); } isNotification(message) { return (message && message.jsonrpc === '2.0' && typeof message.method === 'string' && message.id === undefined); } mapToJsonRpcError(error) { // If already a JSON-RPC error, return as-is if (error && typeof error.code === 'number' && error.message) { return { code: error.code, message: error.message, data: error.data }; } // Map common error types if (error instanceof TypeError) { return { code: JsonRpcErrorCode.INVALID_PARAMS, message: 'Invalid parameters', data: { error: error.message } }; } if (error instanceof SyntaxError) { return { code: JsonRpcErrorCode.PARSE_ERROR, message: 'Parse error', data: { error: error.message } }; } // Default to internal error return { code: JsonRpcErrorCode.INTERNAL_ERROR, message: 'Internal error', data: { error: String(error) } }; } } //# sourceMappingURL=rpc-handler.js.map