UNPKG

@sethdouglasford/claude-flow

Version:

Claude Code Flow - Advanced AI-powered development workflows with SPARC methodology

208 lines 6.64 kB
/** * Standard I/O transport for MCP */ import { stdin, stdout } from "node:process"; import { createInterface } from "node:readline"; import { MCPTransportError } from "../../utils/errors.js"; /** * Stdio transport implementation */ export class StdioTransport { logger; requestHandler; notificationHandler; readline; messageCount = 0; notificationCount = 0; running = false; constructor(logger) { this.logger = logger; } async start() { if (this.running) { throw new MCPTransportError("Transport already running"); } this.logger.info("Starting stdio transport"); try { // Create readline interface for stdin this.readline = createInterface({ input: stdin, output: stdout, terminal: false, }); // Set up line handler this.readline.on("line", (line) => { this.processMessage(line.trim()).catch((error) => { this.logger.error("Error processing message", { line, error }); }); }); this.readline.on("close", () => { this.logger.info("Stdin closed"); this.running = false; }); this.running = true; this.logger.info("Stdio transport started"); } catch (error) { throw new MCPTransportError("Failed to start stdio transport", { error }); } } async stop() { if (!this.running) { return; } this.logger.info("Stopping stdio transport"); this.running = false; if (this.readline) { this.readline.close(); this.readline = undefined; } this.logger.info("Stdio transport stopped"); } onRequest(handler) { this.requestHandler = handler; } onNotification(handler) { this.notificationHandler = handler; } async getHealthStatus() { return { healthy: this.running, metrics: { messagesReceived: this.messageCount, notificationsSent: this.notificationCount, stdinOpen: this.readline ? 1 : 0, }, }; } async processMessage(line) { let message; let rpcMessage; try { message = JSON.parse(line); if (!message || typeof message !== "object" || !("jsonrpc" in message) || message.jsonrpc !== "2.0") { throw new Error("Invalid JSON-RPC version"); } rpcMessage = message; if (!rpcMessage.method) { throw new Error("Missing method"); } } catch (error) { this.logger.error("Failed to parse message", { line, error }); // Send error response if we can extract an ID let id = "unknown"; try { const parsed = JSON.parse(line); if (parsed.id !== undefined) { id = parsed.id; } } catch { // Ignore parse error for ID extraction } await this.sendResponse({ jsonrpc: "2.0", id, error: { code: -32700, message: "Parse error", }, }); return; } this.messageCount++; // Check if this is a notification (no id field) or a request if (rpcMessage.id === undefined) { // This is a notification await this.handleNotification(message); } else { // This is a request await this.handleRequest(message); } } async handleRequest(request) { if (!this.requestHandler) { await this.sendResponse({ jsonrpc: "2.0", id: request.id, error: { code: -32603, message: "No request handler registered", }, }); return; } try { const response = await this.requestHandler(request); await this.sendResponse(response); } catch (error) { this.logger.error("Request handler error", { request, error }); await this.sendResponse({ jsonrpc: "2.0", id: request.id, error: { code: -32603, message: "Internal error", data: error instanceof Error ? error.message : String(error), }, }); } } async handleNotification(notification) { if (!this.notificationHandler) { this.logger.warn("Received notification but no handler registered", { method: notification.method, }); return; } try { await this.notificationHandler(notification); } catch (error) { this.logger.error("Notification handler error", { notification, error }); // Notifications don't send error responses } } async sendResponse(response) { try { const json = JSON.stringify(response); stdout.write(`${json}\n`); } catch (error) { this.logger.error("Failed to send response", { response, error }); } } async connect() { // For STDIO transport, connect is handled by start() if (!this.running) { await this.start(); } } async disconnect() { // For STDIO transport, disconnect is handled by stop() await this.stop(); } async sendRequest(request) { // Send request to stdout const json = JSON.stringify(request); stdout.write(`${json}\n`); // In STDIO transport, responses are handled asynchronously // This would need a proper request/response correlation mechanism throw new Error("STDIO transport sendRequest requires request/response correlation"); } async sendNotification(notification) { try { const json = JSON.stringify(notification); stdout.write(`${json}\n`); this.notificationCount++; } catch (error) { this.logger.error("Failed to send notification", { notification, error }); throw error; } } } //# sourceMappingURL=stdio.js.map