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
JavaScript
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);