Skip to main content
Glama

firefox-devtools-mcp

manager.ts5.68 kB
/** * Snapshot Manager * Handles snapshot creation using bundled injected script */ import type { WebDriver, WebElement } from 'selenium-webdriver'; import { readFileSync } from 'node:fs'; import { dirname, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; import { logDebug } from '../../utils/logger.js'; import type { Snapshot, SnapshotJson, InjectedScriptResult } from './types.js'; import { formatSnapshotTree } from './formatter.js'; import { UidResolver } from './resolver.js'; /** * Snapshot Manager * Uses bundled injected script for snapshot creation */ export class SnapshotManager { private driver: WebDriver; private resolver: UidResolver; private injectedScript: string | null = null; private currentSnapshotId = 0; constructor(driver: WebDriver) { this.driver = driver; this.resolver = new UidResolver(driver); } /** * Lazy load bundled injected script */ private getInjectedScript(): string { if (this.injectedScript) { return this.injectedScript; } try { // Get the directory where this compiled file lives (dist/) const currentFileUrl = import.meta.url; const currentFilePath = fileURLToPath(currentFileUrl); const currentDir = dirname(currentFilePath); // Try multiple potential locations const possiblePaths = [ // Production: relative to compiled dist/index.js location resolve(currentDir, '../../snapshot.injected.global.js'), // Alternative: relative to current working directory resolve(process.cwd(), 'dist/snapshot.injected.global.js'), ]; const attemptedPaths: string[] = []; for (const path of possiblePaths) { attemptedPaths.push(path); try { this.injectedScript = readFileSync(path, 'utf-8'); const sizeKB = (this.injectedScript.length / 1024).toFixed(1); logDebug(`✓ Loaded snapshot bundle: ${path.split('/').pop()} (${sizeKB} KB)`); return this.injectedScript; } catch { // Try next path } } throw new Error( `Bundle not found in any expected location. Tried paths:\n${attemptedPaths.map((p) => ` - ${p}`).join('\n')}` ); } catch (error: any) { throw new Error( `Failed to load bundled snapshot script: ${error.message}. ` + 'Make sure you have run "npm run build" to generate the bundle.' ); } } /** * Take a snapshot of the current page * Returns text and JSON with snapshotId, no DOM mutations */ async takeSnapshot(): Promise<Snapshot> { const snapshotId = ++this.currentSnapshotId; this.resolver.setSnapshotId(snapshotId); this.resolver.clear(); logDebug(`Taking snapshot (ID: ${snapshotId})...`); // Execute bundled injected script const result = await this.executeInjectedScript(snapshotId); logDebug( `Snapshot executeScript result: hasResult=${!!result}, hasTree=${!!result?.tree}, truncated=${result?.truncated || false}` ); // Debug: log isRelevant results if (result?.debugLog && Array.isArray(result.debugLog)) { logDebug(`isRelevant debug log (${result.debugLog.length} elements checked):`); result.debugLog.slice(0, 20).forEach((log: any) => { logDebug(` ${log.relevant ? '✓' : '✗'} ${log.el} (depth ${log.depth})`); }); if (result.debugLog.length > 20) { logDebug(` ... and ${result.debugLog.length - 20} more`); } } if (!result?.tree) { const errorMsg = 'Unknown error'; logDebug(`Snapshot generation failed: ${errorMsg}`); throw new Error(`Failed to generate snapshot: ${errorMsg}`); } // Store UID mappings in resolver this.resolver.storeUidMappings(result.uidMap); // Create snapshot object const snapshotJson: SnapshotJson = { root: result.tree, snapshotId, timestamp: Date.now(), truncated: result.truncated || false, }; const snapshot: Snapshot = { text: formatSnapshotTree(result.tree), json: snapshotJson, }; logDebug( `Snapshot created: ${result.uidMap.length} elements with UIDs${result.truncated ? ' (truncated)' : ''}` ); return snapshot; } /** * Resolve UID to CSS selector (with staleness check) */ resolveUidToSelector(uid: string): string { return this.resolver.resolveUidToSelector(uid); } /** * Resolve UID to WebElement (with staleness check and caching) */ async resolveUidToElement(uid: string): Promise<WebElement> { return await this.resolver.resolveUidToElement(uid); } /** * Clear snapshot (called on navigation) */ clear(): void { this.resolver.clear(); } /** * Execute bundled injected snapshot script */ private async executeInjectedScript(snapshotId: number): Promise<InjectedScriptResult> { const scriptSource = this.getInjectedScript(); // Inject and execute the bundled script // The script exposes window.__createSnapshot via IIFE global // Guard: Only inject once, then reuse const result = await this.driver.executeScript<InjectedScriptResult>( ` // Only inject the bundle if not already present if (typeof window.__createSnapshot === 'undefined') { ${scriptSource} // Register the createSnapshot function globally if (typeof __SnapshotInjected !== 'undefined' && __SnapshotInjected.createSnapshot) { window.__createSnapshot = __SnapshotInjected.createSnapshot; } } // Call it return window.__createSnapshot(arguments[0]); `, snapshotId ); return result; } }

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/freema/firefox-devtools-mcp'

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