Skip to main content
Glama
ooples

MCP Console Automation Server

SessionValidator.ts5.33 kB
import { ConsoleSession } from '../types/index.js'; import { Logger } from '../utils/logger.js'; import { ChildProcess } from 'child_process'; /** * SessionValidator ensures sessions are properly initialized and ready for use * This fixes the "session not found" and "stream destroyed" issues */ export class SessionValidator { private logger: Logger; private sessionReadyMap: Map<string, Promise<boolean>> = new Map(); private sessionHealthMap: Map< string, { lastChecked: Date; healthy: boolean } > = new Map(); private readonly HEALTH_CHECK_INTERVAL = 1000; // 1 second constructor() { this.logger = new Logger('SessionValidator'); } /** * Validate that a session is ready for operations */ async validateSessionReady( sessionId: string, session: ConsoleSession, process?: ChildProcess ): Promise<boolean> { // Check if we're already validating this session const existingValidation = this.sessionReadyMap.get(sessionId); if (existingValidation) { return existingValidation; } // Create validation promise const validationPromise = this.performValidation( sessionId, session, process ); this.sessionReadyMap.set(sessionId, validationPromise); try { const result = await validationPromise; return result; } finally { // Clean up after validation this.sessionReadyMap.delete(sessionId); } } private async performValidation( sessionId: string, session: ConsoleSession, process?: ChildProcess ): Promise<boolean> { this.logger.debug(`Validating session ${sessionId}`); // Check basic session properties if ( !session || session.status === 'stopped' || session.status === 'crashed' ) { this.logger.warn( `Session ${sessionId} is not in a valid state: ${session?.status}` ); return false; } // For local processes, check if the process is still alive if (process) { if (process.killed || process.exitCode !== null) { this.logger.warn(`Session ${sessionId} process has terminated`); return false; } // Check if stdio streams are available if (!process.stdin || process.stdin.destroyed) { this.logger.warn(`Session ${sessionId} stdin is not available`); return false; } // Wait a bit for the process to initialize if (this.isNewSession(session)) { await this.waitForInitialization(sessionId, process); } } // Mark session as healthy this.sessionHealthMap.set(sessionId, { lastChecked: new Date(), healthy: true, }); this.logger.debug(`Session ${sessionId} validation successful`); return true; } private isNewSession(session: ConsoleSession): boolean { const age = Date.now() - session.createdAt.getTime(); return age < 5000; // Less than 5 seconds old } private async waitForInitialization( sessionId: string, process: ChildProcess ): Promise<void> { this.logger.debug(`Waiting for session ${sessionId} to initialize...`); return new Promise((resolve) => { let initialized = false; const timeout = setTimeout(() => { if (!initialized) { this.logger.debug(`Session ${sessionId} initialization timeout`); } resolve(); }, 2000); // Wait max 2 seconds // Listen for initial output as sign of readiness const onData = () => { if (!initialized) { initialized = true; clearTimeout(timeout); this.logger.debug(`Session ${sessionId} initialized`); resolve(); } }; if (process.stdout) { process.stdout.once('data', onData); } if (process.stderr) { process.stderr.once('data', onData); } }); } /** * Check if a session is healthy (cached check) */ isSessionHealthy(sessionId: string): boolean { const health = this.sessionHealthMap.get(sessionId); if (!health) { return false; } // Check if health check is recent const age = Date.now() - health.lastChecked.getTime(); if (age > this.HEALTH_CHECK_INTERVAL) { // Health check is stale return false; } return health.healthy; } /** * Mark a session as unhealthy */ markSessionUnhealthy(sessionId: string): void { this.sessionHealthMap.set(sessionId, { lastChecked: new Date(), healthy: false, }); this.logger.warn(`Session ${sessionId} marked as unhealthy`); } /** * Clean up session validation data */ cleanupSession(sessionId: string): void { this.sessionReadyMap.delete(sessionId); this.sessionHealthMap.delete(sessionId); } /** * Get validation statistics */ getValidationStats(): { totalSessions: number; healthySessions: number; unhealthySessions: number; pendingValidations: number; } { const healthy = Array.from(this.sessionHealthMap.values()).filter( (h) => h.healthy ).length; const unhealthy = this.sessionHealthMap.size - healthy; return { totalSessions: this.sessionHealthMap.size, healthySessions: healthy, unhealthySessions: unhealthy, pendingValidations: this.sessionReadyMap.size, }; } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ooples/mcp-console-automation'

If you have feedback or need assistance with the MCP directory API, please join our Discord server