Skip to main content
Glama
snowfort-ai

Snowfort Circuit MCP

Official
by snowfort-ai
electron-server.js58.1 kB
import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js"; import { ElectronDriver } from "./electron-driver.js"; export class ElectronMCPServer { name; version; server; driver; sessions = new Map(); constructor(name, version = "0.1.0") { this.name = name; this.version = version; this.driver = new ElectronDriver(); this.server = new Server({ name: this.name, version: this.version, }, { capabilities: { tools: {}, }, }); this.setupHandlers(); } setupHandlers() { this.server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: "app_launch", description: "Launch new Electron app instance. For Electron Forge projects, use startScript to auto-start webpack dev server", inputSchema: { type: "object", properties: { app: { type: "string", description: "Path to app executable or project directory (launches new instance)", }, args: { type: "array", items: { type: "string" }, description: "Command line arguments for the application", }, env: { type: "object", description: "Environment variables", }, cwd: { type: "string", description: "Working directory", }, timeout: { type: "number", description: "Launch timeout in milliseconds", }, mode: { type: "string", enum: ["auto", "development", "packaged"], description: "Launch mode: auto-detect, development, or packaged (default: auto)", }, projectPath: { type: "string", description: "Project directory for development mode", }, startScript: { type: "string", description: "npm script to run before launching (e.g., 'start' for Electron Forge). Enhanced detection for Forge patterns, 30s timeout with progress updates", }, electronPath: { type: "string", description: "Custom path to electron executable", }, compressScreenshots: { type: "boolean", description: "Compress screenshots to JPEG (default: true)", }, screenshotQuality: { type: "number", description: "JPEG quality 1-100 (default: 50)", }, disableDevtools: { type: "boolean", description: "Prevent DevTools from opening automatically (default: false)", }, killPortConflicts: { type: "boolean", description: "Automatically kill processes on conflicting ports and retry (default: true)", }, }, required: ["app"], }, }, { name: "click", description: "Click element and get updated window snapshot with element references", inputSchema: { type: "object", properties: { sessionId: { type: "string", description: "Session ID returned from app_launch", }, selector: { type: "string", description: "CSS selector for the element to click", }, windowId: { type: "string", description: "Optional window ID (defaults to main window)", }, }, required: ["sessionId", "selector"], }, }, { name: "type", description: "Type text and get updated window snapshot with element references", inputSchema: { type: "object", properties: { sessionId: { type: "string", description: "Session ID returned from app_launch", }, selector: { type: "string", description: "CSS selector for the element to type into", }, text: { type: "string", description: "Text to type", }, windowId: { type: "string", description: "Optional window ID (defaults to main window)", }, }, required: ["sessionId", "selector", "text"], }, }, { name: "screenshot", description: "Take a compressed screenshot of the current window", inputSchema: { type: "object", properties: { sessionId: { type: "string", description: "Session ID returned from app_launch", }, path: { type: "string", description: "Optional path to save the screenshot", }, windowId: { type: "string", description: "Optional window ID (defaults to main window)", }, }, required: ["sessionId"], }, }, { name: "evaluate", description: "Execute JavaScript in the application context", inputSchema: { type: "object", properties: { sessionId: { type: "string", description: "Session ID returned from app_launch", }, script: { type: "string", description: "JavaScript code to execute", }, windowId: { type: "string", description: "Optional window ID (defaults to main window)", }, }, required: ["sessionId", "script"], }, }, { name: "wait_for_selector", description: "Wait for an element to appear in the application", inputSchema: { type: "object", properties: { sessionId: { type: "string", description: "Session ID returned from app_launch", }, selector: { type: "string", description: "CSS selector to wait for", }, timeout: { type: "number", description: "Timeout in milliseconds (default: 30000)", }, windowId: { type: "string", description: "Optional window ID (defaults to main window)", }, }, required: ["sessionId", "selector"], }, }, { name: "ipc_invoke", description: "Invoke an IPC method in the Electron application", inputSchema: { type: "object", properties: { sessionId: { type: "string", description: "Session ID returned from app_launch", }, channel: { type: "string", description: "IPC channel name", }, args: { type: "array", description: "Arguments to pass to the IPC method", }, }, required: ["sessionId", "channel"], }, }, { name: "get_windows", description: "List windows with type identification (main/devtools/other) and titles", inputSchema: { type: "object", properties: { sessionId: { type: "string", description: "Session ID returned from app_launch", }, }, required: ["sessionId"], }, }, { name: "fs_write_file", description: "Write content to a file", inputSchema: { type: "object", properties: { sessionId: { type: "string", description: "Session ID", }, filePath: { type: "string", description: "Path to the file", }, content: { type: "string", description: "Content to write", }, }, required: ["sessionId", "filePath", "content"], }, }, { name: "fs_read_file", description: "Read content from a file", inputSchema: { type: "object", properties: { sessionId: { type: "string", description: "Session ID", }, filePath: { type: "string", description: "Path to the file", }, }, required: ["sessionId", "filePath"], }, }, { name: "close", description: "Close session and terminate launched Electron app", inputSchema: { type: "object", properties: { sessionId: { type: "string", description: "Session ID to close", }, }, required: ["sessionId"], }, }, { name: "keyboard_press", description: "Press a key or key combination", inputSchema: { type: "object", properties: { sessionId: { type: "string", description: "Session ID returned from app_launch", }, key: { type: "string", description: "Key to press (e.g., 'Enter', 'Tab', 'Escape', 'c')", }, modifiers: { type: "array", items: { type: "string" }, description: "Optional modifier keys (e.g., ['ControlOrMeta'], ['Alt', 'Shift'])", }, windowId: { type: "string", description: "Optional window ID (defaults to main window)", }, }, required: ["sessionId", "key"], }, }, { name: "click_by_text", description: "Click on an element containing specific text", inputSchema: { type: "object", properties: { sessionId: { type: "string", description: "Session ID returned from app_launch", }, text: { type: "string", description: "Text to search for in elements", }, exact: { type: "boolean", description: "Whether to match text exactly (default: false)", }, windowId: { type: "string", description: "Optional window ID (defaults to main window)", }, }, required: ["sessionId", "text"], }, }, { name: "add_locator_handler", description: "Add automatic handler for modals/overlays", inputSchema: { type: "object", properties: { sessionId: { type: "string", description: "Session ID returned from app_launch", }, selector: { type: "string", description: "CSS selector for the modal/overlay to handle", }, action: { type: "string", enum: ["dismiss", "accept", "click"], description: "Action to take when modal appears", }, windowId: { type: "string", description: "Optional window ID (defaults to main window)", }, }, required: ["sessionId", "selector", "action"], }, }, { name: "click_by_role", description: "Click on an element by its accessibility role", inputSchema: { type: "object", properties: { sessionId: { type: "string", description: "Session ID returned from app_launch", }, role: { type: "string", description: "Accessibility role (e.g., 'button', 'link', 'tab', 'textbox')", }, name: { type: "string", description: "Optional accessible name to filter by", }, windowId: { type: "string", description: "Optional window ID (defaults to main window)", }, }, required: ["sessionId", "role"], }, }, { name: "click_nth", description: "Click on the nth element matching a selector", inputSchema: { type: "object", properties: { sessionId: { type: "string", description: "Session ID returned from app_launch", }, selector: { type: "string", description: "CSS selector for the elements", }, index: { type: "number", description: "Zero-based index of the element to click", }, windowId: { type: "string", description: "Optional window ID (defaults to main window)", }, }, required: ["sessionId", "selector", "index"], }, }, { name: "keyboard_type", description: "Type text using keyboard events with optional delay", inputSchema: { type: "object", properties: { sessionId: { type: "string", description: "Session ID returned from app_launch", }, text: { type: "string", description: "Text to type", }, delay: { type: "number", description: "Optional delay between keystrokes in milliseconds", }, windowId: { type: "string", description: "Optional window ID (defaults to main window)", }, }, required: ["sessionId", "text"], }, }, { name: "wait_for_load_state", description: "Wait for the page to reach a specific load state", inputSchema: { type: "object", properties: { sessionId: { type: "string", description: "Session ID returned from app_launch", }, state: { type: "string", enum: ["load", "domcontentloaded", "networkidle"], description: "Load state to wait for (default: 'load')", }, windowId: { type: "string", description: "Optional window ID (defaults to main window)", }, }, required: ["sessionId"], }, }, { name: "snapshot", description: "Get accessibility tree snapshot with element references for AI targeting", inputSchema: { type: "object", properties: { sessionId: { type: "string", description: "Session ID returned from app_launch", }, windowId: { type: "string", description: "Optional window ID (defaults to main window)", }, }, required: ["sessionId"], }, }, { name: "hover", description: "Hover over an element identified by a CSS selector", inputSchema: { type: "object", properties: { sessionId: { type: "string", description: "Session ID returned from app_launch", }, selector: { type: "string", description: "CSS selector for the element to hover over", }, windowId: { type: "string", description: "Optional window ID (defaults to main window)", }, }, required: ["sessionId", "selector"], }, }, { name: "drag", description: "Drag an element to a target location", inputSchema: { type: "object", properties: { sessionId: { type: "string", description: "Session ID returned from app_launch", }, sourceSelector: { type: "string", description: "CSS selector for the element to drag", }, targetSelector: { type: "string", description: "CSS selector for the drop target", }, windowId: { type: "string", description: "Optional window ID (defaults to main window)", }, }, required: ["sessionId", "sourceSelector", "targetSelector"], }, }, { name: "key", description: "Press a keyboard key", inputSchema: { type: "object", properties: { sessionId: { type: "string", description: "Session ID returned from app_launch", }, key: { type: "string", description: "Key to press (e.g., 'Enter', 'Tab', 'Escape')", }, windowId: { type: "string", description: "Optional window ID (defaults to main window)", }, }, required: ["sessionId", "key"], }, }, { name: "select", description: "Select an option from a dropdown", inputSchema: { type: "object", properties: { sessionId: { type: "string", description: "Session ID returned from app_launch", }, selector: { type: "string", description: "CSS selector for the select element", }, value: { type: "string", description: "Value to select", }, windowId: { type: "string", description: "Optional window ID (defaults to main window)", }, }, required: ["sessionId", "selector", "value"], }, }, { name: "upload", description: "Upload a file to an input element", inputSchema: { type: "object", properties: { sessionId: { type: "string", description: "Session ID returned from app_launch", }, selector: { type: "string", description: "CSS selector for the file input element", }, filePath: { type: "string", description: "Path to the file to upload", }, windowId: { type: "string", description: "Optional window ID (defaults to main window)", }, }, required: ["sessionId", "selector", "filePath"], }, }, { name: "back", description: "Navigate back in window history", inputSchema: { type: "object", properties: { sessionId: { type: "string", description: "Session ID returned from app_launch", }, windowId: { type: "string", description: "Optional window ID (defaults to main window)", }, }, required: ["sessionId"], }, }, { name: "forward", description: "Navigate forward in window history", inputSchema: { type: "object", properties: { sessionId: { type: "string", description: "Session ID returned from app_launch", }, windowId: { type: "string", description: "Optional window ID (defaults to main window)", }, }, required: ["sessionId"], }, }, { name: "refresh", description: "Refresh the current window", inputSchema: { type: "object", properties: { sessionId: { type: "string", description: "Session ID returned from app_launch", }, windowId: { type: "string", description: "Optional window ID (defaults to main window)", }, }, required: ["sessionId"], }, }, { name: "content", description: "Get the HTML content of the current window", inputSchema: { type: "object", properties: { sessionId: { type: "string", description: "Session ID returned from app_launch", }, windowId: { type: "string", description: "Optional window ID (defaults to main window)", }, }, required: ["sessionId"], }, }, { name: "text_content", description: "Get the visible text content of the current window", inputSchema: { type: "object", properties: { sessionId: { type: "string", description: "Session ID returned from app_launch", }, windowId: { type: "string", description: "Optional window ID (defaults to main window)", }, }, required: ["sessionId"], }, }, ], }; }); this.server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { const toolArgs = args || {}; switch (name) { case "app_launch": return await this.handleAppLaunch(toolArgs); case "click": await this.handleClick(toolArgs.sessionId, toolArgs.selector, toolArgs.windowId); const clickSnapshot = await this.handleSnapshot(toolArgs.sessionId, toolArgs.windowId); return { content: [ { type: "text", text: "Element clicked successfully" }, { type: "text", text: `Window Snapshot:\n${clickSnapshot}` } ] }; case "type": await this.handleType(toolArgs.sessionId, toolArgs.selector, toolArgs.text, toolArgs.windowId); const typeSnapshot = await this.handleSnapshot(toolArgs.sessionId, toolArgs.windowId); return { content: [ { type: "text", text: "Text typed successfully" }, { type: "text", text: `Window Snapshot:\n${typeSnapshot}` } ] }; case "screenshot": const screenshotPath = await this.handleScreenshot(toolArgs.sessionId, toolArgs.path, toolArgs.windowId); return { content: [{ type: "text", text: `Screenshot saved to: ${screenshotPath}` }] }; case "evaluate": const evalResult = await this.handleEvaluate(toolArgs.sessionId, toolArgs.script, toolArgs.windowId); return { content: [{ type: "text", text: `Result: ${JSON.stringify(evalResult)}` }] }; case "wait_for_selector": await this.handleWaitForSelector(toolArgs.sessionId, toolArgs.selector, toolArgs.timeout, toolArgs.windowId); return { content: [{ type: "text", text: "Element found" }] }; case "ipc_invoke": return await this.handleIpcInvoke(toolArgs.sessionId, toolArgs.channel, toolArgs.args || []); case "get_windows": return await this.handleGetWindows(toolArgs.sessionId); case "fs_write_file": await this.handleWriteFile(toolArgs.sessionId, toolArgs.filePath, toolArgs.content); return { content: [{ type: "text", text: "File written successfully" }] }; case "fs_read_file": return await this.handleReadFile(toolArgs.sessionId, toolArgs.filePath); case "close": await this.handleClose(toolArgs.sessionId); return { content: [{ type: "text", text: "Session closed successfully" }] }; case "keyboard_press": await this.handleKeyboardPress(toolArgs.sessionId, toolArgs.key, toolArgs.modifiers, toolArgs.windowId); const kbPressSnapshot = await this.handleSnapshot(toolArgs.sessionId, toolArgs.windowId); return { content: [ { type: "text", text: "Key pressed successfully" }, { type: "text", text: `Window Snapshot:\n${kbPressSnapshot}` } ] }; case "click_by_text": await this.handleClickByText(toolArgs.sessionId, toolArgs.text, toolArgs.exact, toolArgs.windowId); const clickTextSnapshot = await this.handleSnapshot(toolArgs.sessionId, toolArgs.windowId); return { content: [ { type: "text", text: "Element clicked by text successfully" }, { type: "text", text: `Window Snapshot:\n${clickTextSnapshot}` } ] }; case "add_locator_handler": await this.handleAddLocatorHandler(toolArgs.sessionId, toolArgs.selector, toolArgs.action, toolArgs.windowId); return { content: [{ type: "text", text: "Locator handler added successfully" }] }; case "click_by_role": await this.handleClickByRole(toolArgs.sessionId, toolArgs.role, toolArgs.name, toolArgs.windowId); const clickRoleSnapshot = await this.handleSnapshot(toolArgs.sessionId, toolArgs.windowId); return { content: [ { type: "text", text: "Element clicked by role successfully" }, { type: "text", text: `Window Snapshot:\n${clickRoleSnapshot}` } ] }; case "click_nth": await this.handleClickNth(toolArgs.sessionId, toolArgs.selector, toolArgs.index, toolArgs.windowId); const clickNthSnapshot = await this.handleSnapshot(toolArgs.sessionId, toolArgs.windowId); return { content: [ { type: "text", text: "Nth element clicked successfully" }, { type: "text", text: `Window Snapshot:\n${clickNthSnapshot}` } ] }; case "keyboard_type": await this.handleKeyboardType(toolArgs.sessionId, toolArgs.text, toolArgs.delay, toolArgs.windowId); const kbTypeSnapshot = await this.handleSnapshot(toolArgs.sessionId, toolArgs.windowId); return { content: [ { type: "text", text: "Text typed successfully" }, { type: "text", text: `Window Snapshot:\n${kbTypeSnapshot}` } ] }; case "wait_for_load_state": await this.handleWaitForLoadState(toolArgs.sessionId, toolArgs.state, toolArgs.windowId); return { content: [{ type: "text", text: "Page load state reached" }] }; case "snapshot": const snapshotResult = await this.handleSnapshot(toolArgs.sessionId, toolArgs.windowId); return { content: [{ type: "text", text: snapshotResult }] }; case "hover": await this.handleHover(toolArgs.sessionId, toolArgs.selector, toolArgs.windowId); const hoverSnapshot = await this.handleSnapshot(toolArgs.sessionId, toolArgs.windowId); return { content: [ { type: "text", text: "Element hovered successfully" }, { type: "text", text: `Window Snapshot:\n${hoverSnapshot}` } ] }; case "drag": await this.handleDrag(toolArgs.sessionId, toolArgs.sourceSelector, toolArgs.targetSelector, toolArgs.windowId); const dragSnapshot = await this.handleSnapshot(toolArgs.sessionId, toolArgs.windowId); return { content: [ { type: "text", text: "Element dragged successfully" }, { type: "text", text: `Window Snapshot:\n${dragSnapshot}` } ] }; case "key": await this.handleKey(toolArgs.sessionId, toolArgs.key, toolArgs.windowId); const keySnapshot = await this.handleSnapshot(toolArgs.sessionId, toolArgs.windowId); return { content: [ { type: "text", text: `Key '${toolArgs.key}' pressed successfully` }, { type: "text", text: `Window Snapshot:\n${keySnapshot}` } ] }; case "select": await this.handleSelect(toolArgs.sessionId, toolArgs.selector, toolArgs.value, toolArgs.windowId); const selectSnapshot = await this.handleSnapshot(toolArgs.sessionId, toolArgs.windowId); return { content: [ { type: "text", text: "Option selected successfully" }, { type: "text", text: `Window Snapshot:\n${selectSnapshot}` } ] }; case "upload": await this.handleUpload(toolArgs.sessionId, toolArgs.selector, toolArgs.filePath, toolArgs.windowId); const uploadSnapshot = await this.handleSnapshot(toolArgs.sessionId, toolArgs.windowId); return { content: [ { type: "text", text: "File uploaded successfully" }, { type: "text", text: `Window Snapshot:\n${uploadSnapshot}` } ] }; case "back": await this.handleBack(toolArgs.sessionId, toolArgs.windowId); return { content: [{ type: "text", text: "Navigated back successfully" }] }; case "forward": await this.handleForward(toolArgs.sessionId, toolArgs.windowId); return { content: [{ type: "text", text: "Navigated forward successfully" }] }; case "refresh": await this.handleRefresh(toolArgs.sessionId, toolArgs.windowId); const refreshSnapshot = await this.handleSnapshot(toolArgs.sessionId, toolArgs.windowId); return { content: [ { type: "text", text: "Window refreshed successfully" }, { type: "text", text: `Window Snapshot:\n${refreshSnapshot}` } ] }; case "content": const htmlContent = await this.handleContent(toolArgs.sessionId, toolArgs.windowId); return { content: [{ type: "text", text: htmlContent }] }; case "text_content": const textContent = await this.handleTextContent(toolArgs.sessionId, toolArgs.windowId); return { content: [{ type: "text", text: textContent }] }; default: throw new Error(`Unknown tool: ${name}`); } } catch (error) { return { content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}` }], isError: true, }; } }); } async getSession(sessionId) { const session = this.sessions.get(sessionId); if (!session) { throw new Error(`Session not found: ${sessionId}`); } return session; } async handleAppLaunch(args) { let debugInfo = []; try { const opts = { app: args.app, args: args.args, env: args.env, cwd: args.cwd, timeout: args.timeout, mode: args.mode, projectPath: args.projectPath, startScript: args.startScript, electronPath: args.electronPath, compressScreenshots: args.compressScreenshots, screenshotQuality: args.screenshotQuality, disableDevtools: args.disableDevtools, killPortConflicts: args.killPortConflicts, }; debugInfo.push(`[DEBUG] Launch attempt for app: ${opts.app}`); debugInfo.push(`[DEBUG] Mode: ${opts.mode || 'auto'}`); debugInfo.push(`[DEBUG] Project path: ${opts.projectPath || 'not specified'}`); debugInfo.push(`[DEBUG] CWD: ${opts.cwd || process.cwd()}`); const session = await this.driver.launch(opts); this.sessions.set(session.id, session); return { content: [ { type: "text", text: `Electron app launched successfully. Session ID: ${session.id}`, }, ], }; } catch (error) { // Capture additional debug info for the error response const errorMessage = error instanceof Error ? error.message : String(error); // Include debug information in the error response so it appears in MCP logs const fullErrorMessage = [ `Failed to launch Electron app: ${errorMessage}`, '', '=== DEBUG INFO ===', ...debugInfo, `[DEBUG] Final error: ${errorMessage}`, `[DEBUG] Error type: ${error?.constructor?.name || 'Unknown'}`, '==================' ].join('\n'); return { content: [ { type: "text", text: fullErrorMessage, }, ], isError: true, }; } } async handleClick(sessionId, selector, windowId) { const session = await this.getSession(sessionId); await this.driver.click(session, selector, windowId); } async handleType(sessionId, selector, text, windowId) { const session = await this.getSession(sessionId); await this.driver.type(session, selector, text, windowId); } async handleScreenshot(sessionId, path, windowId) { const session = await this.getSession(sessionId); return await this.driver.screenshot(session, path, windowId); } async handleEvaluate(sessionId, script, windowId) { const session = await this.getSession(sessionId); return await this.driver.evaluate(session, script, windowId); } async handleWaitForSelector(sessionId, selector, timeout, windowId) { const session = await this.getSession(sessionId); await this.driver.waitForSelector(session, selector, timeout, windowId); } async handleIpcInvoke(sessionId, channel, args) { const session = await this.getSession(sessionId); const result = await this.driver.invokeIPC(session, channel, ...args); return { content: [ { type: "text", text: `IPC result: ${JSON.stringify(result)}`, }, ], }; } async handleGetWindows(sessionId) { const session = await this.getSession(sessionId); const windows = await this.driver.getWindows(session); const windowDescriptions = windows.map(w => `${w.id} (${w.type}${w.title ? `: "${w.title}"` : ''})`); return { content: [ { type: "text", text: `Available windows:\n${windowDescriptions.join("\n")}`, }, ], }; } async handleWriteFile(sessionId, filePath, content) { const session = await this.getSession(sessionId); await this.driver.writeFile(session, filePath, content); } async handleReadFile(sessionId, filePath) { const session = await this.getSession(sessionId); const content = await this.driver.readFile(session, filePath); return { content: [ { type: "text", text: content, }, ], }; } async handleKeyboardPress(sessionId, key, modifiers, windowId) { const session = await this.getSession(sessionId); await this.driver.keyboardPress(session, key, modifiers, windowId); } async handleClickByText(sessionId, text, exact, windowId) { const session = await this.getSession(sessionId); await this.driver.clickByText(session, text, exact || false, windowId); } async handleAddLocatorHandler(sessionId, selector, action, windowId) { const session = await this.getSession(sessionId); await this.driver.addLocatorHandler(session, selector, action, windowId); } async handleClickByRole(sessionId, role, name, windowId) { const session = await this.getSession(sessionId); await this.driver.clickByRole(session, role, name, windowId); } async handleClickNth(sessionId, selector, index, windowId) { const session = await this.getSession(sessionId); await this.driver.clickNth(session, selector, index, windowId); } async handleKeyboardType(sessionId, text, delay, windowId) { const session = await this.getSession(sessionId); await this.driver.keyboardType(session, text, delay, windowId); } async handleWaitForLoadState(sessionId, state, windowId) { const session = await this.getSession(sessionId); await this.driver.waitForLoadState(session, state, windowId); } async handleSnapshot(sessionId, windowId) { const session = await this.getSession(sessionId); return await this.driver.snapshot(session, windowId); } async handleHover(sessionId, selector, windowId) { const session = await this.getSession(sessionId); await this.driver.hover(session, selector, windowId); } async handleDrag(sessionId, sourceSelector, targetSelector, windowId) { const session = await this.getSession(sessionId); await this.driver.drag(session, sourceSelector, targetSelector, windowId); } async handleKey(sessionId, key, windowId) { const session = await this.getSession(sessionId); await this.driver.key(session, key, windowId); } async handleSelect(sessionId, selector, value, windowId) { const session = await this.getSession(sessionId); await this.driver.select(session, selector, value, windowId); } async handleUpload(sessionId, selector, filePath, windowId) { const session = await this.getSession(sessionId); await this.driver.upload(session, selector, filePath, windowId); } async handleBack(sessionId, windowId) { const session = await this.getSession(sessionId); await this.driver.back(session, windowId); } async handleForward(sessionId, windowId) { const session = await this.getSession(sessionId); await this.driver.forward(session, windowId); } async handleRefresh(sessionId, windowId) { const session = await this.getSession(sessionId); await this.driver.refresh(session, windowId); } async handleContent(sessionId, windowId) { const session = await this.getSession(sessionId); return await this.driver.content(session, windowId); } async handleTextContent(sessionId, windowId) { const session = await this.getSession(sessionId); return await this.driver.textContent(session, windowId); } async handleClose(sessionId) { const session = await this.getSession(sessionId); await this.driver.close(session); this.sessions.delete(sessionId); } async cleanup() { console.error("[ELECTRON-MCP] Cleaning up server resources..."); // Close all active sessions for (const [sessionId, session] of this.sessions) { try { await this.driver.close(session); } catch (error) { console.error(`[ELECTRON-MCP] Error closing session ${sessionId}:`, error); } } this.sessions.clear(); } async run() { try { const transport = new StdioServerTransport(); // Enhanced transport error handling transport.onerror = (error) => { console.error("[ELECTRON-MCP] Transport error:", error); console.error("[ELECTRON-MCP] Transport error stack:", error.stack); }; transport.onclose = () => { console.error("[ELECTRON-MCP] Transport closed - connection terminated"); console.error("[ELECTRON-MCP] Active sessions:", this.sessions.size); // Log but don't exit - the client may reconnect }; // Add additional process event handlers process.stdin.on('error', (error) => { console.error("[ELECTRON-MCP] stdin error:", error); }); process.stdout.on('error', (error) => { console.error("[ELECTRON-MCP] stdout error:", error); }); process.stderr.on('error', (error) => { console.error("[ELECTRON-MCP] stderr error:", error); }); console.error("[ELECTRON-MCP] Connecting transport..."); console.error("[ELECTRON-MCP] Process PID:", process.pid); console.error("[ELECTRON-MCP] Node version:", process.version); console.error("[ELECTRON-MCP] Platform:", process.platform); await this.server.connect(transport); console.error("[ELECTRON-MCP] Transport connected successfully"); // Enhanced connection monitoring const keepAlive = setInterval(() => { console.error("[ELECTRON-MCP] Heartbeat - transport active, sessions:", this.sessions.size); }, 30000); // Every 30 seconds // Keep process alive with multiple fallbacks process.stdin.resume(); process.stdin.setEncoding('utf8'); // Setup cleanup handlers but don't return a promise that blocks const cleanup = () => { clearInterval(keepAlive); console.error("[ELECTRON-MCP] Server shutting down gracefully"); }; process.on("disconnect", () => { console.error("[ELECTRON-MCP] Process disconnected"); cleanup(); process.exit(0); }); process.on("SIGPIPE", () => { console.error("[ELECTRON-MCP] SIGPIPE received - broken pipe"); cleanup(); process.exit(0); }); // Don't return a hanging promise - let the server.connect() promise resolve normally console.error("[ELECTRON-MCP] Server ready for requests"); } catch (error) { console.error("[ELECTRON-MCP] Failed to connect transport:", error); console.error("[ELECTRON-MCP] Error details:", error instanceof Error ? error.stack : error); throw error; } } } //# sourceMappingURL=electron-server.js.map

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/snowfort-ai/circuit-mcp'

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