Skip to main content
Glama

Selenium MCP Server

server.ts9.68 kB
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StateManager } from './utils/helpers.js'; import { registerAllTools } from './tools/index.js'; import { registerBrowserStatusResource } from './resources/browserStatus.js'; import { readFileSync } from 'fs'; import { fileURLToPath } from 'url'; import { dirname, join } from 'path'; import { Logger } from './utils/logger.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const configPath = join(__dirname, '..', 'version.config.json'); const CLEANUP_TIMEOUT_MS = 10000; // 10 seconds for overall cleanup const SESSION_QUIT_TIMEOUT_MS = 5000; // 5 seconds per session quit operation interface VersionConfig { name: string; version: string; displayName?: string; } // Load version config with proper error handling let versionConfig: VersionConfig; const configContent = readFileSync(configPath, 'utf8'); versionConfig = JSON.parse(configContent); export class SeleniumMcpServer { private readonly server: McpServer; private readonly stateManager: StateManager; private readonly startTime: number; private isShuttingDown: boolean = false; private isInitialized: boolean = false; constructor() { try { this.server = new McpServer({ name: versionConfig.name, version: versionConfig.version, }); this.stateManager = new StateManager(); this.startTime = Date.now(); Logger.debug('✅ SeleniumMcpServer constructor completed'); } catch (error) { Logger.error('❌ Error in SeleniumMcpServer constructor:', error); throw error; } } public initialize(): void { try { Logger.info('🔧 Initializing Selenium MCP Server...'); // Register all tools registerAllTools(this.server, this.stateManager); Logger.debug('✅ Tools registered'); // Register resources registerBrowserStatusResource(this.server, this.stateManager); Logger.debug('✅ Resources registered'); // Setup cleanup handlers this.setupCleanup(); Logger.debug('✅ Cleanup handlers registered'); this.isInitialized = true; Logger.info('✅ Server initialization completed'); } catch (error) { Logger.error('❌ Error during server initialization:', error); throw error; } } private setupCleanup(): void { const cleanup = async (signal?: string): Promise<void> => { if (this.isShuttingDown) { Logger.warn('⚠️ Shutdown already in progress...'); return; } this.isShuttingDown = true; try { if (signal) { Logger.info(`🛑 ${signal} received, initiating graceful shutdown...`); } else { Logger.info('🛑 Initiating graceful shutdown...'); } Logger.info('🧹 Cleaning up browser sessions...'); const state = this.stateManager.getState(); if (state.drivers.size > 0) { const cleanupPromises: Promise<void>[] = []; for (const [sessionId, driver] of state.drivers) { cleanupPromises.push(this.cleanupSession(sessionId, driver)); } // Wait for all cleanup operations with a timeout await Promise.race([ Promise.allSettled(cleanupPromises), new Promise(resolve => setTimeout(resolve, CLEANUP_TIMEOUT_MS)), ]); } this.stateManager.clearDrivers(); this.stateManager.resetCurrentSession(); Logger.info('✅ Cleanup completed successfully'); } catch (error) { Logger.error('❌ Error during cleanup:', error); } }; // Register cleanup handlers - don't exit process in cleanup process.on('SIGTERM', () => { cleanup('SIGTERM') .then(() => { process.exit(0); }) .catch((error: unknown) => { Logger.error('Error during SIGTERM cleanup:', error); process.exit(1); }); }); process.on('SIGINT', () => { cleanup('SIGINT') .then(() => { process.exit(0); }) .catch((error: unknown) => { Logger.error('Error during SIGINT cleanup:', error); process.exit(1); }); }); // Handle uncaught exceptions process.on('uncaughtException', (error: Error) => { Logger.error('💥 Uncaught Exception:', error); cleanup('uncaughtException') .then(() => { process.exit(1); }) .catch(() => { process.exit(1); }); }); // Handle unhandled promise rejections process.on('unhandledRejection', (reason: unknown, promise: Promise<unknown>) => { Logger.error('🚫 Unhandled Rejection at:', promise, 'reason:', reason); cleanup('unhandledRejection') .then(() => { process.exit(1); }) .catch(() => { process.exit(1); }); }); } private async cleanupSession(sessionId: string, driver: unknown): Promise<void> { try { Logger.debug(`🧹 Cleaning up session: ${sessionId}`); // Type guard to ensure driver has quit method if (driver && typeof driver === 'object' && 'quit' in driver) { const webDriver = driver as { quit: () => Promise<void> }; // Add timeout to quit operation await Promise.race([ webDriver.quit(), new Promise((_, reject) => setTimeout(() => reject(new Error('Quit timeout')), SESSION_QUIT_TIMEOUT_MS)), ]); } Logger.debug(`✅ Session ${sessionId} cleaned up successfully`); } catch (error) { Logger.error(`❌ Error closing browser session ${sessionId}:`, error); // Don't throw - just log and continue with other cleanups } } public getServer(): McpServer { return this.server; } public getStateManager(): StateManager { return this.stateManager; } public isReady(): boolean { return this.isInitialized && !this.isShuttingDown; } public async start(): Promise<void> { if (this.isShuttingDown) { throw new Error('Cannot start server while shutting down'); } if (!this.isInitialized) { throw new Error('Server must be initialized before starting'); } try { Logger.info('🚀 Starting Selenium MCP Server...'); Logger.info('✅ Selenium MCP Server started successfully'); } catch (error) { Logger.error('❌ Failed to start Selenium MCP Server:', error); throw error; } } public async stop(): Promise<void> { if (this.isShuttingDown) { Logger.warn('⚠️ Server is already shutting down...'); return; } try { this.isShuttingDown = true; Logger.info('🛑 Stopping Selenium MCP Server...'); // Clean up all sessions const state = this.stateManager.getState(); if (state.drivers.size > 0) { const cleanupPromises: Promise<void>[] = []; for (const [sessionId, driver] of state.drivers) { cleanupPromises.push(this.cleanupSession(sessionId, driver)); } // Wait for cleanup with timeout await Promise.race([ Promise.allSettled(cleanupPromises), new Promise(resolve => setTimeout(resolve, CLEANUP_TIMEOUT_MS)), ]); } // Clear state this.stateManager.clearDrivers(); this.stateManager.resetCurrentSession(); Logger.info('✅ Selenium MCP Server stopped successfully'); } catch (error) { Logger.error('❌ Error stopping Selenium MCP Server:', error); throw error; } } public getHealthStatus(): { status: 'healthy' | 'unhealthy'; activeSessions: number; serverName: string; version: string; uptime: number; } { const state = this.stateManager.getState(); return { status: this.isInitialized && !this.isShuttingDown ? 'healthy' : 'unhealthy', activeSessions: state.drivers.size, serverName: versionConfig.name, version: versionConfig.version, uptime: process.uptime(), }; } public getStats(): { server: { name: string; version: string; uptime: number; memoryUsage: NodeJS.MemoryUsage; isShuttingDown: boolean; }; sessions: { total: number; active: number; sessionIds: string[]; currentSession: string | null; }; } { const state = this.stateManager.getState(); const sessionIds = Array.from(state.drivers.keys()); return { server: { name: versionConfig.name, version: versionConfig.version, uptime: process.uptime(), memoryUsage: process.memoryUsage(), isShuttingDown: this.isShuttingDown, }, sessions: { total: state.drivers.size, active: state.drivers.size, sessionIds, currentSession: state.currentSession, }, }; } } export function createSeleniumMcpServer(options?: { autoStart?: boolean; enableHealthLogging?: boolean; shutdownTimeout?: number; }): SeleniumMcpServer { try { Logger.info('🏗️ Creating Selenium MCP Server...'); const server = new SeleniumMcpServer(); Logger.debug('🔧 Initializing server...'); server.initialize(); if (options?.autoStart) { server.start().catch((error: unknown) => { Logger.error('Failed to auto-start server:', error); process.exit(1); }); } Logger.info('✅ Selenium MCP Server created successfully'); return server; } catch (error) { Logger.error('❌ Failed to create Selenium MCP Server:', error); throw error; } } export default SeleniumMcpServer;

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/pshivapr/selenium-mcp'

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