@sethdouglasford/claude-flow
Version:
Claude Code Flow - Advanced AI-powered development workflows with SPARC methodology
208 lines • 6.64 kB
JavaScript
/**
* 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