UNPKG

supapup

Version:

โšก Lightning-fast MCP browser dev tool. Navigate โ†’ Get instant structured data. No screenshots needed! Puppeteer: ๐Ÿ“ธ โ†’ CSS selectors โ†’ JS eval. Supapup: semantic IDs ready to use. 10x faster, 90% fewer tokens.

778 lines (777 loc) โ€ข 38 kB
#!/usr/bin/env node 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 { EventEmitter } from 'events'; import { BrowserRecovery } from './browser-recovery.js'; // Fix MaxListenersExceededWarning EventEmitter.defaultMaxListeners = 50; process.setMaxListeners(50); // Warning suppression (same as original) process.removeAllListeners('warning'); process.on('warning', (warning) => { const warningText = warning.toString(); if (warningText.includes('MaxListenersExceededWarning') || warning.name === 'MaxListenersExceededWarning' || warningText.includes('Chrome') || warningText.includes('puppeteer') || warningText.includes('DevTools') || warningText.includes('Protocol error')) { return; } }); const originalWarning = process.emitWarning; process.emitWarning = (warning, options) => { const warningText = typeof warning === 'string' ? warning : warning.toString(); if (warningText.includes('MaxListenersExceededWarning') || warningText.includes('Chrome') || warningText.includes('puppeteer') || warningText.includes('DevTools') || warningText.includes('Protocol error')) { return; } originalWarning.call(process, warning, options); }; // Stderr suppression (same as original) const originalStderrWrite = process.stderr.write; process.stderr.write = function (chunk, ...args) { const text = chunk?.toString() || ''; if (text.includes('DevTools listening') || text.includes('MaxListenersExceededWarning') || text.includes('Possible EventTarget memory leak detected') || text.includes('abort listeners added') || text.includes('(Use `node --trace-warnings') || text.includes('TargetCloseError') || text.includes('Protocol error') || (text.includes('[') && text.includes(']') && text.includes('ERROR')) || (text.includes('(node:') && text.includes(')')) || text.includes('Chrome') || text.includes('puppeteer')) { return true; } // @ts-ignore return originalStderrWrite.apply(process.stderr, [chunk, ...args]); }; // Import tool classes import { BrowserTools } from './../tools/browser-tools.js'; import { AgentTools } from './../tools/agent-tools.js'; import { ScreenshotTools } from './../tools/screenshot-tools.js'; import { ServerTools } from './server-tools.js'; import { ToolDefinitions } from './tool-definitions.js'; import { NetworkTools } from './../tools/network-tools.js'; import { FormTools } from './../tools/form-tools.js'; import { HumanInteraction } from './../tools/human-interaction.js'; import { PageAnalysis } from './../tools/page-analysis.js'; import { DevToolsElements } from './../tools/devtools-elements.js'; import { StorageTools } from './../tools/storage-tools.js'; import { ViewportTools } from './../tools/viewport-tools.js'; import { getDefaultHeadlessForRole } from './role-definitions.js'; export class SupapupServer { server; browserRecovery; // New tool classes browserTools; agentTools; screenshotTools; serverTools; // Existing specialized tools devtools = null; responsiveTester = null; networkTools = null; pageAnalysis = null; devToolsElements = null; storageTools = null; formTools = null; humanInteraction = null; viewportTools = null; role; constructor(role) { this.role = role; this.server = new Server({ name: 'supapup', version: '0.1.15', }, { capabilities: { tools: {}, }, }); this.browserRecovery = new BrowserRecovery(); // Initialize new tool classes this.browserTools = new BrowserTools(this.browserRecovery); this.agentTools = new AgentTools(); this.screenshotTools = new ScreenshotTools(); this.serverTools = new ServerTools(); this.setupHandlers(); } setupHandlers() { this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: ToolDefinitions.getToolDefinitions(this.role) })); // Crash recovery wrapper this.server.setRequestHandler(CallToolRequestSchema, async (request) => { try { return await this.handleToolRequest(request); } catch (error) { console.error('๐Ÿ’ฅ Tool request crashed:', error); // Check if this is a browser crash const browserHealthy = await this.browserRecovery.checkBrowserHealth(this.browserTools.getBrowser(), this.browserTools.getPage()); if (!browserHealthy) { console.log('๐Ÿ”ง Browser crash detected, cleaning up...'); await this.handleBrowserCrash(); return { content: [{ type: 'text', text: `๐Ÿ’ฅ Browser crashed and has been cleaned up. Error: ${error.message}\n\n` + `๐Ÿ”„ You can continue by calling browser_navigate again.` }] }; } // Non-browser crash, provide helpful error with suggestions let errorMessage = `โŒ Tool error: ${error.message}`; // Add contextual help based on common error patterns if (error.message.includes('No page available')) { errorMessage += `\n\n๐Ÿ’ก Quick fix: Use browser_navigate to open a webpage first.`; } else if (error.message.includes('not found') || error.message.includes('not available')) { errorMessage += `\n\n๐Ÿ’ก Troubleshooting:\n- Use agent_generate_page to see available elements\n- Check if the page has finished loading\n- Try agent_remap_page if elements disappeared after page changes`; } else if (error.message.includes('timeout') || error.message.includes('waiting')) { errorMessage += `\n\n๐Ÿ’ก Timeout help:\n- Increase timeout parameter if content is slow to load\n- Check if loading indicators are still visible\n- Use specific selectors with waitForSelector parameter`; } else if (error.message.includes('navigation') || error.message.includes('redirect')) { errorMessage += `\n\n๐Ÿ’ก Navigation tip: Try browser_navigate again or check if the URL is accessible.`; } return { content: [{ type: 'text', text: errorMessage }] }; } }); } async handleToolRequest(request) { const toolName = request.params.name; const args = request.params.arguments || {}; // Initialize tools if we have a page await this.initializeToolsIfNeeded(); switch (toolName) { // Browser Tools case 'browser_navigate': const result = await this.browserTools.navigate(args.url, args.visible); // Update all tools with new page and manifest const newPage = this.browserTools.getPage(); if (newPage) { this.agentTools.initialize(newPage, this.browserTools.getCurrentManifest(), this.browserTools); this.screenshotTools.initialize(newPage); // Update specialized tools with new page reference if (this.formTools) this.formTools.page = newPage; if (this.networkTools) this.networkTools.page = newPage; if (this.humanInteraction) this.humanInteraction.page = newPage; if (this.pageAnalysis) this.pageAnalysis.page = newPage; if (this.devToolsElements) this.devToolsElements.page = newPage; if (this.storageTools) await this.storageTools.initialize(newPage); if (this.viewportTools) this.viewportTools.page = newPage; } return result; case 'browser_close': return await this.browserTools.closeBrowser(); case 'browser_set_visibility': return await this.browserTools.setBrowserVisibility(args.visible, args.restart); case 'browser_open_in_tab': return await this.browserTools.openInTab(args.content, args.contentType, args.title); case 'browser_list_tabs': return await this.browserTools.listTabs(); case 'browser_switch_tab': const switchResult = await this.browserTools.switchTab(args.index); // Update tool references to new page const switchPage = this.browserTools.getPage(); if (switchPage) { this.agentTools.initialize(switchPage, this.browserTools.getCurrentManifest(), this.browserTools); } const page = this.browserTools.getPage(); if (page) { this.screenshotTools.initialize(page); } return switchResult; // Agent Tools case 'agent_execute_action': // Refresh all tool page references before action execution this.refreshAllToolPageReferences(); return await this.agentTools.executeAction(args.actionId, args.params); case 'agent_get_page_state': return await this.agentTools.getPageState(); case 'agent_discover_actions': return await this.agentTools.discoverActions(); case 'agent_generate_page': return await this.agentTools.generatePage(args.enhanced, args.mode); case 'agent_remap_page': return await this.agentTools.remapPage(args.timeout, args.waitForSelector); case 'agent_wait_for_changes': return await this.agentTools.waitForChanges(args); case 'agent_get_page_chunk': return await this.agentTools.getPageChunk(args.page, args.maxElements); case 'get_agent_page_chunk': return await this.agentTools.getAgentPageChunk(args.id, args.chunk); case 'agent_read_content': return await this.agentTools.readContent(args); // Screenshot Tools case 'screenshot_capture': return await this.screenshotTools.capture(args); case 'screenshot_paginated': return await this.screenshotTools.capturePaginated(args); case 'screenshot_get_chunk': return await this.screenshotTools.getChunk(args.id, args.chunk); case 'browser_navigate_and_capture_loading_sequence': return await this.browserTools.navigateAndCaptureLoadingSequence(args.url, args, this.screenshotTools); case 'get_loading_sequence_frame': return await this.screenshotTools.getLoadingSequenceFrame(args.id, args.frame); // Server Tools case 'server_info': return await this.serverTools.getServerInfo(); // Form Tools (delegated to existing classes) case 'form_fill': if (!this.formTools) throw new Error('Form tools not initialized'); // Update page reference before operation const currentPageForFill = this.browserTools.getPage(); if (currentPageForFill) this.formTools.page = currentPageForFill; // Extract formData and options from args const { formData, formId, submitAfter, validateRequired } = args; // Validate that formData is provided and is an object if (!formData || typeof formData !== 'object') { throw new Error(`formData must be an object, received: ${typeof formData}`); } // Ensure agent tools are initialized and generate page to add data-mcp-id attributes await this.initializeToolsIfNeeded(); if (!this.agentTools) throw new Error('Agent tools not initialized'); await this.agentTools.generatePage(); const fillResult = await this.formTools.fillForm(formData, { formId, submitAfter, validateRequired }); // Format the result for MCP response let responseText = ''; if (fillResult.success) { responseText = `โœ… Form filled successfully!\n\n`; if (fillResult.filled.length > 0) { responseText += `๐Ÿ“ Fields filled (${fillResult.filled.length}):\n`; fillResult.filled.forEach(field => { responseText += ` โ€ข ${field}\n`; }); } } else { responseText = `โŒ Form fill failed\n\n`; } if (fillResult.errors.length > 0) { responseText += `\nโ— Errors:\n`; fillResult.errors.forEach(error => { responseText += ` โ€ข ${error}\n`; }); } if (fillResult.warnings.length > 0) { responseText += `\nโš ๏ธ Warnings:\n`; fillResult.warnings.forEach(warning => { responseText += ` โ€ข ${warning}\n`; }); } return { content: [{ type: 'text', text: responseText.trim() }] }; case 'form_detect': if (!this.formTools) throw new Error('Form tools not initialized'); // Update page reference before operation const currentPageForDetect = this.browserTools.getPage(); if (currentPageForDetect) this.formTools.page = currentPageForDetect; return await this.formTools.detectForms(); case 'form_ask_human': if (!this.humanInteraction) throw new Error('Human interaction not initialized'); return await this.humanInteraction.askHumanToIdentifyElement(args.prompt, args.timeout); // Network Tools case 'network_get_console_logs': if (!this.networkTools) throw new Error('Network tools not initialized'); return await this.networkTools.getConsoleLogs(args); case 'network_get_logs': if (!this.networkTools) throw new Error('Network tools not initialized'); return await this.networkTools.getNetworkLogs(args); case 'network_get_api_logs': if (!this.networkTools) throw new Error('Network tools not initialized'); return await this.networkTools.getAPILogs(args); case 'network_clear_logs': if (!this.networkTools) throw new Error('Network tools not initialized'); return await this.networkTools.clearLogs(); case 'network_debug_all_logs': if (!this.networkTools) throw new Error('Network tools not initialized'); return await this.networkTools.debugAllLogs(); case 'network_replay_request': if (!this.networkTools) throw new Error('Network tools not initialized'); return await this.networkTools.replayAPIRequest(args); case 'network_intercept_requests': if (!this.networkTools) throw new Error('Network tools not initialized'); return await this.networkTools.interceptRequests(args); case 'network_throttle': if (!this.networkTools) throw new Error('Network tools not initialized'); // This method might not exist - need to check implementation return { content: [{ type: 'text', text: 'Network throttling not yet implemented' }] }; // Page Analysis Tools case 'page_get_resources': if (!this.pageAnalysis) throw new Error('Page analysis not initialized'); return await this.pageAnalysis.getPageResources(); case 'page_get_performance': if (!this.pageAnalysis) throw new Error('Page analysis not initialized'); return await this.pageAnalysis.getPerformanceMetrics(); case 'page_get_accessibility': if (!this.pageAnalysis) throw new Error('Page analysis not initialized'); return await this.pageAnalysis.getAccessibilityTree(); case 'page_get_console_logs': // Alias for network_get_console_logs - console logs aren't really network-related if (!this.networkTools) throw new Error('Network tools not initialized'); return await this.networkTools.getConsoleLogs(args); case 'page_inspect_element': if (!this.pageAnalysis) throw new Error('Page analysis not initialized'); return await this.pageAnalysis.inspectElement(args); case 'page_evaluate_script': if (!this.pageAnalysis) throw new Error('Page analysis not initialized'); // Update page reference before operation const currentPageForEval = this.browserTools.getPage(); if (currentPageForEval) this.pageAnalysis.page = currentPageForEval; return await this.pageAnalysis.evaluateScript(args); case 'page_execute_and_wait': if (!this.pageAnalysis) throw new Error('Page analysis not initialized'); // Update page reference before operation const currentPageForExecute = this.browserTools.getPage(); if (currentPageForExecute) this.pageAnalysis.page = currentPageForExecute; return await this.pageAnalysis.executeAndWait(args); case 'page_trigger_hover': if (!this.pageAnalysis) throw new Error('Page analysis not initialized'); // Update page reference before operation const currentPageForHover = this.browserTools.getPage(); if (currentPageForHover) this.pageAnalysis.page = currentPageForHover; return await this.pageAnalysis.captureHoverSequence(args); case 'page_trigger_hover_get_frame': if (!this.pageAnalysis) throw new Error('Page analysis not initialized'); return await this.pageAnalysis.getHoverAnimationFrame(args); // DevTools Elements case 'devtools_inspect_element': if (!this.devToolsElements) throw new Error('DevTools elements not initialized'); return await this.devToolsElements.inspectElement(args); case 'devtools_modify_css': if (!this.devToolsElements) throw new Error('DevTools elements not initialized'); return await this.devToolsElements.modifyCSS(args); case 'devtools_highlight_element': if (!this.devToolsElements) throw new Error('DevTools elements not initialized'); return await this.devToolsElements.highlightElement(args); case 'devtools_modify_html': if (!this.devToolsElements) throw new Error('DevTools elements not initialized'); return await this.devToolsElements.modifyHTML(args); case 'devtools_get_computed_styles': if (!this.devToolsElements) throw new Error('DevTools elements not initialized'); return await this.devToolsElements.getComputedStyles(args); case 'devtools_visual_element_map': if (!this.devToolsElements) throw new Error('DevTools elements not initialized'); return await this.devToolsElements.createVisualElementMap(args); // Storage Tools case 'storage_get': if (!this.storageTools) throw new Error('Storage tools not initialized'); return await this.handleStorageGet(args); case 'storage_set': if (!this.storageTools) throw new Error('Storage tools not initialized'); return await this.handleStorageSet(args); case 'storage_remove': if (!this.storageTools) throw new Error('Storage tools not initialized'); return await this.handleStorageRemove(args); case 'storage_clear': if (!this.storageTools) throw new Error('Storage tools not initialized'); return await this.handleStorageClear(args); case 'storage_export_state': if (!this.storageTools) throw new Error('Storage tools not initialized'); return await this.handleStorageExport(); case 'storage_import_state': if (!this.storageTools) throw new Error('Storage tools not initialized'); return await this.handleStorageImport(args); case 'storage_get_info': if (!this.storageTools) throw new Error('Storage tools not initialized'); return await this.handleStorageGetInfo(); // Viewport Tools case 'viewport_set': await this.initializeToolsIfNeeded(); if (!this.viewportTools) throw new Error('Viewport tools not initialized'); const viewportResult = await this.viewportTools.setViewport(args); return { content: [{ type: 'text', text: `โœ… Viewport set to ${viewportResult.viewport.width}x${viewportResult.viewport.height}` + (args.zoom ? ` @ ${args.zoom}% zoom` : '') + (args.preset ? ` (${args.preset})` : '') + `\n\n๐Ÿ“ฑ Mobile: ${viewportResult.viewport.isMobile}` + `\n๐Ÿ–ฑ๏ธ Touch: ${viewportResult.viewport.hasTouch || false}` + `\n๐Ÿ“ Device Scale Factor: ${viewportResult.viewport.deviceScaleFactor}` }] }; case 'viewport_test_responsive': await this.initializeToolsIfNeeded(); if (!this.viewportTools) throw new Error('Viewport tools not initialized'); const testResults = await this.viewportTools.testResponsiveBreakpoints(args.breakpoints); let resultText = '๐Ÿ“ฑ Responsive Design Test Results:\n\n'; testResults.forEach(result => { resultText += `๐Ÿ“ ${result.width}px:\n`; result.issues.forEach(issue => { resultText += ` ${issue.includes('No issues') ? 'โœ…' : 'โš ๏ธ'} ${issue}\n`; }); resultText += '\n'; }); return { content: [{ type: 'text', text: resultText }] }; // Responsive Section Testing Tools case 'test_responsive': await this.initializeToolsIfNeeded(); if (!this.viewportTools) throw new Error('Viewport tools not initialized'); const responsiveResult = await this.viewportTools.testResponsive(args.url); let responsiveText = `๐Ÿ“ฑ Responsive Analysis Complete!\n\n`; responsiveText += `๐ŸŒ URL: ${responsiveResult.url}\n`; responsiveText += `๐Ÿ“‹ Sections Found: ${responsiveResult.sectionsFound}\n\n`; responsiveText += `๐Ÿ“‘ Page Sections:\n`; responsiveResult.sections.forEach(section => { responsiveText += ` ${section.id}. ${section.type} (${section.description})\n`; responsiveText += ` ๐Ÿ“ ${section.bounds.width}ร—${section.bounds.height} at (${section.bounds.x}, ${section.bounds.y})\n`; }); responsiveText += `\n${responsiveResult.message}`; return { content: [{ type: 'text', text: responsiveText }, { type: 'image', data: responsiveResult.fullPagePreview, mimeType: 'image/png' }] }; case 'test_responsive_section': await this.initializeToolsIfNeeded(); if (!this.viewportTools) throw new Error('Viewport tools not initialized'); const sectionResult = await this.viewportTools.testResponsiveSection(args.sectionId); if (sectionResult.screenshotsCacheId) { // Paginated response let paginatedText = `๐Ÿ“ฑ Section ${sectionResult.sectionId} Screenshots Captured!\n\n`; paginatedText += `๐ŸŽฏ Section: ${sectionResult.sectionType} (${sectionResult.sectionDescription})\n`; paginatedText += `๐Ÿ“ฑ Devices Tested: ${sectionResult.devicesTestedCount}\n\n`; paginatedText += `๐Ÿ“‹ Available Devices:\n`; sectionResult.devices.forEach(device => { paginatedText += ` ${device.index}. ${device.device} (${device.preset}) - ${device.viewport.width}ร—${device.viewport.height}\n`; }); paginatedText += `\n${sectionResult.message}`; return { content: [{ type: 'text', text: paginatedText }] }; } else { // Direct response with all screenshots let directText = `๐Ÿ“ฑ Section ${sectionResult.sectionId} Responsive Test Results!\n\n`; directText += `๐ŸŽฏ Section: ${sectionResult.sectionType} (${sectionResult.sectionDescription})\n`; directText += `๐Ÿ“ฑ Devices Tested: ${sectionResult.devicesTestedCount}\n\n`; const content = [{ type: 'text', text: directText }]; sectionResult.screenshots?.forEach((screenshot, index) => { content.push({ type: 'text', text: `๐Ÿ“ฑ ${screenshot.device} (${screenshot.viewport.width}ร—${screenshot.viewport.height}):` }); content.push({ type: 'image', data: screenshot.screenshot, mimeType: 'image/png' }); }); return { content }; } case 'test_responsive_section_get_screenshot': await this.initializeToolsIfNeeded(); if (!this.viewportTools) throw new Error('Viewport tools not initialized'); const cachedScreenshot = await this.viewportTools.getResponsiveSectionScreenshot(args.cacheId, args.deviceIndex); return { content: [{ type: 'text', text: `๐Ÿ“ฑ ${cachedScreenshot.device} Screenshot (${cachedScreenshot.viewport.width}ร—${cachedScreenshot.viewport.height})\n` + `Device ${cachedScreenshot.deviceIndex + 1} of ${cachedScreenshot.totalDevices}` }, { type: 'image', data: cachedScreenshot.screenshot, mimeType: 'image/png' }] }; default: throw new Error(`Unknown tool: ${toolName}`); } } async initializeToolsIfNeeded() { const page = this.browserTools.getPage(); if (!page) return; // Initialize existing tools if needed if (!this.networkTools) { this.networkTools = new NetworkTools(page); } if (!this.formTools) { this.formTools = new FormTools(page); } else { // Update page reference for existing formTools this.formTools.page = page; } if (!this.humanInteraction) { this.humanInteraction = new HumanInteraction(page); } if (!this.storageTools) { this.storageTools = new StorageTools(); await this.storageTools.initialize(page); } if (!this.devToolsElements) { this.devToolsElements = new DevToolsElements(); const client = await page.target().createCDPSession(); await this.devToolsElements.initialize(page, client); } if (!this.pageAnalysis) { this.pageAnalysis = new PageAnalysis(page); } if (!this.viewportTools) { this.viewportTools = new ViewportTools(page); } } /** * Refresh all tool page references to prevent detached frame errors */ refreshAllToolPageReferences() { const currentPage = this.browserTools.getPage(); if (!currentPage) return; // Update all tool page references if (this.formTools) this.formTools.page = currentPage; if (this.networkTools) this.networkTools.page = currentPage; if (this.humanInteraction) this.humanInteraction.page = currentPage; if (this.pageAnalysis) this.pageAnalysis.page = currentPage; if (this.devToolsElements) this.devToolsElements.page = currentPage; // These tools don't exist as class properties - removed if (this.devtools) this.devtools.page = currentPage; if (this.responsiveTester) this.responsiveTester.page = currentPage; } async handleBrowserCrash() { console.log('๐Ÿงน Cleaning up crashed browser...'); await this.browserRecovery.cleanupCrashedBrowser(this.browserTools.getBrowser()); // Record crash for tracking this.browserRecovery.recordCrash('Browser crashed - cleaned up and reset'); // Clear browser references to prevent infinite loop this.browserTools.clearBrowserReferences(); // Reset tool states this.agentTools.initialize(null, null, null); this.screenshotTools.initialize(null); // Reset existing tools this.networkTools = null; this.formTools = null; this.humanInteraction = null; this.storageTools = null; this.devToolsElements = null; this.pageAnalysis = null; this.browserRecovery.resetState(); } // Storage helper methods async handleStorageGet(args) { const { type = 'all' } = args; let result = {}; if (type === 'all' || type === 'localStorage') { result.localStorage = await this.storageTools.getLocalStorage(); } if (type === 'all' || type === 'sessionStorage') { result.sessionStorage = await this.storageTools.getSessionStorage(); } if (type === 'all' || type === 'cookies') { result.cookies = await this.storageTools.getCookies(); } return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } async handleStorageSet(args) { const { type, key, value } = args; if (type === 'localStorage') { await this.storageTools.setLocalStorage(key, value); } else if (type === 'sessionStorage') { await this.storageTools.setSessionStorage(key, value); } else { throw new Error('Invalid storage type. Use localStorage or sessionStorage'); } return { content: [{ type: 'text', text: `Storage ${type} set: ${key} = ${value}` }] }; } async handleStorageRemove(args) { const { type, key } = args; if (type === 'localStorage') { await this.storageTools.removeLocalStorage(key); } else if (type === 'sessionStorage') { await this.storageTools.removeSessionStorage(key); } else { throw new Error('Invalid storage type. Use localStorage or sessionStorage'); } return { content: [{ type: 'text', text: `Storage ${type} removed: ${key}` }] }; } async handleStorageClear(args) { const { type = 'all' } = args; if (type === 'all') { await this.storageTools.clearAllStorage(); } else if (type === 'localStorage') { await this.storageTools.clearLocalStorage(); } else if (type === 'sessionStorage') { await this.storageTools.clearSessionStorage(); } else if (type === 'cookies') { await this.storageTools.clearCookies(); } else { throw new Error('Invalid storage type. Use all, localStorage, sessionStorage, or cookies'); } return { content: [{ type: 'text', text: `Storage ${type} cleared` }] }; } async handleStorageExport() { const state = await this.storageTools.exportStorageState(); return { content: [{ type: 'text', text: JSON.stringify(state, null, 2) }] }; } async handleStorageImport(args) { const { state } = args; await this.storageTools.importStorageState(state); return { content: [{ type: 'text', text: 'Storage state imported successfully' }] }; } async handleStorageGetInfo() { const info = await this.storageTools.getStorageInfo(); return { content: [{ type: 'text', text: JSON.stringify(info, null, 2) }] }; } async run() { const transport = new StdioServerTransport(); await this.server.connect(transport); // Show role and tool count information const tools = ToolDefinitions.getToolDefinitions(this.role); const roleInfo = this.role ? `(${this.role} role: ${tools.length} tools)` : `(${tools.length} tools)`; // Get version from package.json let version = ''; try { const fs = await import('fs'); const path = await import('path'); const { fileURLToPath } = await import('url'); const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const packagePath = path.resolve(__dirname, '../../package.json'); const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8')); version = `v${packageJson.version} `; } catch (error) { // Fallback - version will be empty string } console.log(`๐Ÿค– Supapup ${version}MCP server running ${roleInfo}`); } } // Parse command line arguments and environment variables function parseArgs() { const args = process.argv.slice(2); let role; let headless; // Parse command line arguments for (let i = 0; i < args.length; i++) { if (args[i] === '--role' && i + 1 < args.length) { role = args[i + 1]; i++; // Skip next argument as it's the role value } else if (args[i] === '--headless') { headless = true; } } // Check environment variables if (!role && process.env.SUPAPUP_ROLE) { role = process.env.SUPAPUP_ROLE; } if (headless === undefined && process.env.SUPAPUP_HEADLESS) { headless = process.env.SUPAPUP_HEADLESS.toLowerCase() === 'true'; } return { role, headless }; } // Run the server const { role, headless } = parseArgs(); // Apply role-based defaults and explicit settings // Set headless mode: explicit flag > environment variable > role default > existing default let finalHeadless = headless; if (finalHeadless === undefined) { finalHeadless = getDefaultHeadlessForRole(role); } if (finalHeadless !== undefined) { process.env.SUPAPUP_HEADLESS = finalHeadless.toString(); } const server = new SupapupServer(role); server.run().catch(console.error);