Skip to main content
Glama

Lighthouse MCP

by mizchi
database.tsโ€ข8.85 kB
import { DatabaseSync } from 'node:sqlite'; import { existsSync, mkdirSync } from 'fs'; import { dirname } from 'path'; import type { LighthouseReport } from '../types/lighthouse.js'; export interface CrawlResult { id?: number; url: string; device: 'mobile' | 'desktop'; timestamp: string; performance_score: number | null; accessibility_score: number | null; best_practices_score: number | null; seo_score: number | null; pwa_score: number | null; fcp: number | null; lcp: number | null; cls: number | null; tbt: number | null; tti: number | null; si: number | null; report_json: string; error: string | null; created_at?: string; } export interface AnalysisResult { id?: number; crawl_id: number; tool_name: string; analysis_type: string; result_json: string; created_at?: string; } export class LighthouseDatabase { private db: DatabaseSync; private readonly dbPath: string; constructor(dbPath: string = '.lhdata/results.db') { this.dbPath = dbPath; this.ensureDirectory(); this.db = new DatabaseSync(this.dbPath); this.initDatabase(); } private ensureDirectory(): void { const dir = dirname(this.dbPath); if (!existsSync(dir)) { mkdirSync(dir, { recursive: true }); } } private initDatabase(): void { // Create crawl_results table this.db.exec(` CREATE TABLE IF NOT EXISTS crawl_results ( id INTEGER PRIMARY KEY AUTOINCREMENT, url TEXT NOT NULL, device TEXT NOT NULL CHECK(device IN ('mobile', 'desktop')), timestamp TEXT NOT NULL, performance_score REAL, accessibility_score REAL, best_practices_score REAL, seo_score REAL, pwa_score REAL, fcp REAL, lcp REAL, cls REAL, tbt REAL, tti REAL, si REAL, report_json TEXT NOT NULL, error TEXT, created_at TEXT DEFAULT CURRENT_TIMESTAMP, UNIQUE(url, device, timestamp) ) `); // Create analysis_results table this.db.exec(` CREATE TABLE IF NOT EXISTS analysis_results ( id INTEGER PRIMARY KEY AUTOINCREMENT, crawl_id INTEGER NOT NULL, tool_name TEXT NOT NULL, analysis_type TEXT NOT NULL, result_json TEXT NOT NULL, created_at TEXT DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (crawl_id) REFERENCES crawl_results(id) ON DELETE CASCADE, UNIQUE(crawl_id, tool_name, analysis_type) ) `); // Create indexes for better query performance this.db.exec(` CREATE INDEX IF NOT EXISTS idx_crawl_url ON crawl_results(url); CREATE INDEX IF NOT EXISTS idx_crawl_device ON crawl_results(device); CREATE INDEX IF NOT EXISTS idx_crawl_timestamp ON crawl_results(timestamp); CREATE INDEX IF NOT EXISTS idx_crawl_performance ON crawl_results(performance_score); CREATE INDEX IF NOT EXISTS idx_analysis_crawl ON analysis_results(crawl_id); CREATE INDEX IF NOT EXISTS idx_analysis_tool ON analysis_results(tool_name); `); } /** * Save a Lighthouse report to the database */ saveCrawlResult(report: LighthouseReport, device: 'mobile' | 'desktop' = 'mobile'): number { const stmt = this.db.prepare(` INSERT OR REPLACE INTO crawl_results ( url, device, timestamp, performance_score, accessibility_score, best_practices_score, seo_score, pwa_score, fcp, lcp, cls, tbt, tti, si, report_json, error ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? ) `); const categories = report.categories || {}; const audits = report.audits || {}; const result = stmt.run( report.finalUrl || report.requestedUrl, device, report.fetchTime, categories.performance?.score ?? null, categories.accessibility?.score ?? null, categories['best-practices']?.score ?? null, categories.seo?.score ?? null, categories.pwa?.score ?? null, audits['first-contentful-paint']?.numericValue ?? null, audits['largest-contentful-paint']?.numericValue ?? null, audits['cumulative-layout-shift']?.numericValue ?? null, audits['total-blocking-time']?.numericValue ?? null, audits['interactive']?.numericValue ?? null, audits['speed-index']?.numericValue ?? null, JSON.stringify(report), report.runtimeError?.message ?? null, ); return Number(result.lastInsertRowId); } /** * Save an analysis result */ saveAnalysisResult( crawlId: number, toolName: string, analysisType: string, result: any ): number { const stmt = this.db.prepare(` INSERT OR REPLACE INTO analysis_results ( crawl_id, tool_name, analysis_type, result_json ) VALUES ( ?, ?, ?, ? ) `); const insertResult = stmt.run( crawlId, toolName, analysisType, JSON.stringify(result), ); return insertResult.lastInsertRowId as number; } /** * Get crawl results by URL */ getCrawlsByUrl(url: string, limit: number = 10): CrawlResult[] { const stmt = this.db.prepare(` SELECT * FROM crawl_results WHERE url = ? ORDER BY timestamp DESC LIMIT ? `); return stmt.all(url, limit) as CrawlResult[]; } /** * Get latest crawl result for a URL and device */ getLatestCrawl(url: string, device: 'mobile' | 'desktop' = 'mobile'): CrawlResult | null { const stmt = this.db.prepare(` SELECT * FROM crawl_results WHERE url = ? AND device = ? ORDER BY timestamp DESC LIMIT 1 `); return stmt.get(url, device) as CrawlResult | null; } /** * Get analysis results for a crawl */ getAnalysisResults(crawlId: number): AnalysisResult[] { const stmt = this.db.prepare(` SELECT * FROM analysis_results WHERE crawl_id = ? ORDER BY created_at DESC `); return stmt.all(crawlId) as AnalysisResult[]; } /** * Get performance trends for a URL */ getPerformanceTrends( url: string, device: 'mobile' | 'desktop' = 'mobile', days: number = 30 ): CrawlResult[] { const stmt = this.db.prepare(` SELECT * FROM crawl_results WHERE url = ? AND device = ? AND datetime(timestamp) >= datetime('now', '-' || ? || ' days') ORDER BY timestamp ASC `); return stmt.all(url, device, days) as CrawlResult[]; } /** * Get all unique URLs in the database */ getAllUrls(): string[] { const stmt = this.db.prepare(` SELECT DISTINCT url FROM crawl_results ORDER BY url `); return stmt.all().map((row: any) => row.url); } /** * Get crawl statistics */ getStatistics(): { totalCrawls: number; uniqueUrls: number; avgPerformanceScore: number; recentCrawls: CrawlResult[]; } { const totalCrawls = this.db .prepare('SELECT COUNT(*) as count FROM crawl_results') .get() as { count: number }; const uniqueUrls = this.db .prepare('SELECT COUNT(DISTINCT url) as count FROM crawl_results') .get() as { count: number }; const avgPerformance = this.db .prepare('SELECT AVG(performance_score) as avg FROM crawl_results WHERE performance_score IS NOT NULL') .get() as { avg: number }; const recentCrawls = this.db .prepare(` SELECT * FROM crawl_results ORDER BY created_at DESC LIMIT 10 `) .all() as CrawlResult[]; return { totalCrawls: totalCrawls.count, uniqueUrls: uniqueUrls.count, avgPerformanceScore: avgPerformance.avg || 0, recentCrawls, }; } /** * Clean old records */ cleanOldRecords(daysToKeep: number = 90): number { const stmt = this.db.prepare(` DELETE FROM crawl_results WHERE datetime(created_at) < datetime('now', '-' || ? || ' days') `); const result = stmt.run(daysToKeep); return result.changes; } /** * Close the database connection */ /** * Get all crawl results (for analysis) */ getAllCrawlResults(): CrawlResult[] { const stmt = this.db.prepare(` SELECT * FROM crawl_results ORDER BY timestamp DESC `); return stmt.all() as CrawlResult[]; } /** * Get all crawl results as simplified reports format */ getReports(): Array<{ id: number; data: string }> { const stmt = this.db.prepare(` SELECT id, report_json as data FROM crawl_results ORDER BY timestamp DESC `); return stmt.all() as Array<{ id: number; data: string }>; } close(): void { this.db.close(); } /** * Execute a raw SQL query (for advanced use cases) */ query(sql: string, params: any[] = []): any[] { const stmt = this.db.prepare(sql); return stmt.all(...params); } }

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/mizchi/lighthouse-mcp'

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