Skip to main content
Glama

Lighthouse MCP

by mizchi
l2-third-party-impact.tsโ€ข14.6 kB
/** * L2 Third-Party Impact Analysis Tool * Analyzes the impact of third-party scripts on performance */ import { executeL1GetReport } from './l1-get-report.js'; import { executeL1Collect } from './l1-collect-single.js'; import { analyzeThirdPartyImpact, compareThirdPartyImpact, getThirdPartyDomains, type ThirdPartyAnalysis, type ThirdPartyComparison } from '../analyzers/thirdParty.js'; import type { LighthouseReport } from '../types/index.js'; export interface L2ThirdPartyImpactParams { reportId?: string; url?: string; device?: 'mobile' | 'desktop'; compareMode?: 'analyze' | 'compare' | 'domains'; blockDomains?: string[]; gather?: boolean; } export interface L2ThirdPartyImpactResult { mode: 'analyze' | 'compare' | 'domains'; reportId?: string; analysis?: ThirdPartyAnalysis; comparison?: ThirdPartyComparison; domains?: string[]; recommendations?: string[]; } export const l2ThirdPartyImpactTool = { name: 'l2_third_party_impact', description: 'Analyze third-party script impact on performance (Layer 2)', inputSchema: { type: 'object', properties: { reportId: { type: 'string', description: 'Report ID to analyze', }, url: { type: 'string', description: 'URL to analyze (if no reportId)', }, device: { type: 'string', enum: ['mobile', 'desktop'], default: 'mobile', description: 'Device type', }, compareMode: { type: 'string', enum: ['analyze', 'compare', 'domains'], default: 'analyze', description: 'Analysis mode: analyze (single), compare (with/without blocking), domains (list domains)', }, blockDomains: { type: 'array', items: { type: 'string' }, description: 'Domains to block for comparison mode', }, gather: { type: 'boolean', default: false, description: 'Force fresh data collection', }, }, }, execute: async (params: any) => { const result = await executeL2ThirdPartyImpact(params); return { type: 'text', text: JSON.stringify(result, null, 2) }; } }; export async function executeL2ThirdPartyImpact(params: L2ThirdPartyImpactParams): Promise<L2ThirdPartyImpactResult> { const mode = params.compareMode || 'analyze'; if (mode === 'analyze') { // Single report analysis let reportId = params.reportId; let report: LighthouseReport; if (reportId) { const result = await executeL1GetReport({ reportId }); report = result.data; } else if (params.url) { const collectResult = await executeL1Collect({ url: params.url, device: params.device || 'mobile', categories: ['performance'], gather: params.gather || false, }); reportId = collectResult.reportId; const result = await executeL1GetReport({ reportId }); report = result.data; } else { throw new Error('Either reportId or url is required'); } const analysis = analyzeThirdPartyImpact(report); if (!analysis) { return { mode: 'analyze', reportId, recommendations: ['No third-party scripts detected'], }; } return { mode: 'analyze', reportId, analysis, recommendations: analysis.recommendations, }; } else if (mode === 'domains') { // Get list of third-party domains let report: LighthouseReport; if (params.reportId) { const result = await executeL1GetReport({ reportId: params.reportId }); report = result.data; } else if (params.url) { const collectResult = await executeL1Collect({ url: params.url, device: params.device || 'mobile', categories: ['performance'], gather: params.gather || false, }); const result = await executeL1GetReport({ reportId: collectResult.reportId }); report = result.data; } else { throw new Error('Either reportId or url is required'); } const domains = getThirdPartyDomains(report); const analysis = analyzeThirdPartyImpact(report); const recommendations: string[] = []; if (domains.length > 0) { recommendations.push(`Found ${domains.length} third-party domains`); recommendations.push('Use these domains with blockDomains parameter to test impact'); // Group domains by impact if (analysis?.entities) { const highImpactDomains = new Set<string>(); for (const entity of analysis.entities.filter(e => e.blockingTime > 100)) { for (const subRequest of entity.subRequests) { try { const url = new URL(subRequest.url); highImpactDomains.add(url.hostname); } catch { // Invalid URL } } } if (highImpactDomains.size > 0) { recommendations.push(`High-impact domains (>100ms blocking): ${Array.from(highImpactDomains).slice(0, 5).join(', ')}`); } } } return { mode: 'domains', domains, analysis: analysis === null ? undefined : analysis, recommendations, }; } else if (mode === 'compare') { // Compare with and without blocking third-party scripts if (!params.url) { throw new Error('URL is required for compare mode'); } // First, collect without blocking (normal) const withThirdPartyResult = await executeL1Collect({ url: params.url, device: params.device || 'mobile', categories: ['performance'], gather: true, // Always gather fresh for comparison }); const withThirdPartyReport = await executeL1GetReport({ reportId: withThirdPartyResult.reportId }); // Then collect with blocking let blockDomains = params.blockDomains; // If no domains specified, get all third-party domains if (!blockDomains || blockDomains.length === 0) { blockDomains = getThirdPartyDomains(withThirdPartyReport.data); } if (blockDomains.length === 0) { return { mode: 'compare', recommendations: ['No third-party domains found to block'], }; } // Collect with blocked domains const baselineResult = await executeL1Collect({ url: params.url, device: params.device || 'mobile', categories: ['performance'], gather: true, blockDomains, }); const baselineReport = await executeL1GetReport({ reportId: baselineResult.reportId }); // Compare the two reports const comparison = compareThirdPartyImpact( baselineReport.data, withThirdPartyReport.data ); // Add additional recommendations const recommendations = [...comparison.recommendations]; if (comparison.impact.scoreDelta > 0.05) { recommendations.push(`Consider lazy-loading or deferring ${blockDomains.length} third-party scripts`); } // Analyze which specific third parties have the most impact const thirdPartyAnalysis = analyzeThirdPartyImpact(withThirdPartyReport.data); if (thirdPartyAnalysis) { const topOffenders = thirdPartyAnalysis.entities .filter(e => e.blockingTime > 100) .slice(0, 3); if (topOffenders.length > 0) { recommendations.push('Top third-party performance impacts:'); for (const entity of topOffenders) { recommendations.push( `- ${entity.entity}: ${Math.round(entity.blockingTime)}ms blocking, ${Math.round(entity.transferSize / 1024)}KB` ); } } } return { mode: 'compare', comparison, analysis: thirdPartyAnalysis === null ? undefined : thirdPartyAnalysis, domains: blockDomains, recommendations, }; } throw new Error(`Unknown mode: ${mode}`); } /** * Progressive third-party analysis * Analyzes impact by progressively blocking third-party scripts */ export interface L2ProgressiveThirdPartyParams { url: string; device?: 'mobile' | 'desktop'; maxIterations?: number; } export interface L2ProgressiveThirdPartyResult { baseline: { score: number; metrics: any; }; iterations: Array<{ blockedDomains: string[]; score: number; scoreDelta: number; metrics: any; recommendation: string; }>; optimalBlocking: string[]; recommendations: string[]; } export const l2ProgressiveThirdPartyTool = { name: 'l2_progressive_third_party', description: 'Progressive third-party impact analysis (Layer 2)', inputSchema: { type: 'object', properties: { url: { type: 'string', description: 'URL to analyze', }, device: { type: 'string', enum: ['mobile', 'desktop'], default: 'mobile', description: 'Device type', }, maxIterations: { type: 'number', default: 5, description: 'Maximum number of progressive blocking iterations', }, }, required: ['url'], }, execute: async (params: any) => { const result = await executeL2ProgressiveThirdParty(params); return { type: 'text', text: JSON.stringify(result, null, 2) }; } }; export async function executeL2ProgressiveThirdParty( params: L2ProgressiveThirdPartyParams ): Promise<L2ProgressiveThirdPartyResult> { const { url, device = 'mobile', maxIterations = 5 } = params; // Get baseline (all third-party allowed) const baselineResult = await executeL1Collect({ url, device, categories: ['performance'], gather: true, }); const baselineReport = await executeL1GetReport({ reportId: baselineResult.reportId }); const baselineMetrics = { score: baselineReport.data.categories?.performance?.score || 0, fcp: baselineReport.data.audits?.['first-contentful-paint']?.numericValue || 0, lcp: baselineReport.data.audits?.['largest-contentful-paint']?.numericValue || 0, tbt: baselineReport.data.audits?.['total-blocking-time']?.numericValue || 0, cls: baselineReport.data.audits?.['cumulative-layout-shift']?.numericValue || 0, }; // Get third-party analysis const thirdPartyAnalysis = analyzeThirdPartyImpact(baselineReport.data); if (!thirdPartyAnalysis || thirdPartyAnalysis.entities.length === 0) { return { baseline: { score: baselineMetrics.score, metrics: baselineMetrics, }, iterations: [], optimalBlocking: [], recommendations: ['No third-party scripts detected'], }; } // Sort entities by blocking time (most impactful first) const sortedEntities = [...thirdPartyAnalysis.entities] .sort((a, b) => b.blockingTime - a.blockingTime); const iterations = []; const blockedDomains: string[] = []; let bestScore = baselineMetrics.score; let optimalBlocking: string[] = []; // Progressive blocking for (let i = 0; i < Math.min(maxIterations, sortedEntities.length); i++) { const entity = sortedEntities[i]; // Get domains for this entity const entityDomains = new Set<string>(); for (const subRequest of entity.subRequests) { try { const url = new URL(subRequest.url); entityDomains.add(url.hostname); } catch { // Invalid URL } } // Add to blocked list blockedDomains.push(...Array.from(entityDomains)); // Test with blocking const iterationResult = await executeL1Collect({ url, device, categories: ['performance'], gather: true, blockDomains: [...blockedDomains], }); const iterationReport = await executeL1GetReport({ reportId: iterationResult.reportId }); const iterationMetrics = { score: iterationReport.data.categories?.performance?.score || 0, fcp: iterationReport.data.audits?.['first-contentful-paint']?.numericValue || 0, lcp: iterationReport.data.audits?.['largest-contentful-paint']?.numericValue || 0, tbt: iterationReport.data.audits?.['total-blocking-time']?.numericValue || 0, cls: iterationReport.data.audits?.['cumulative-layout-shift']?.numericValue || 0, }; const scoreDelta = iterationMetrics.score - baselineMetrics.score; iterations.push({ iteration: i, blockedDomains: [...blockedDomains], score: iterationMetrics.score, scoreDelta, metrics: iterationMetrics, recommendation: `Blocking ${entity.entity} improves score by ${Math.round(scoreDelta * 100)} points`, }); // Track best configuration if (iterationMetrics.score > bestScore) { bestScore = iterationMetrics.score; optimalBlocking = [...blockedDomains]; } } // Generate recommendations const recommendations: string[] = []; const totalImprovement = bestScore - baselineMetrics.score; if (totalImprovement > 0.05) { recommendations.push( `Blocking ${optimalBlocking.length} domains can improve performance score by ${Math.round(totalImprovement * 100)} points` ); } // Find the point of diminishing returns let significantIterations = 0; for (const iteration of iterations) { if (iteration.scoreDelta > 0.02) { significantIterations++; } } if (significantIterations > 0) { recommendations.push( `${significantIterations} third-party script(s) have significant performance impact (>2 points)` ); } // Specific recommendations for high-impact scripts for (let i = 0; i < Math.min(3, iterations.length); i++) { const iteration = iterations[i]; if (iteration.scoreDelta > 0.05) { const entity = sortedEntities[i]; recommendations.push( `Critical: ${entity.entity} causes ${Math.round(entity.blockingTime)}ms blocking time` ); } } const summaryParts: string[] = []; summaryParts.push(`Baseline performance score ${(baselineMetrics.score * 100).toFixed(0)} points`); if (iterations.length === 0) { summaryParts.push('No significant third-party impact detected.'); } else if (totalImprovement > 0.05 && optimalBlocking.length > 0) { summaryParts.push( `Blocking ${optimalBlocking.length} domain${optimalBlocking.length === 1 ? '' : 's'} improved the score by ${Math.round(totalImprovement * 100)} points.` ); } else { summaryParts.push('Blocking the analyzed domains did not produce a measurable improvement.'); } // const summary = summaryParts.join(' '); return { baseline: { score: baselineMetrics.score, metrics: baselineMetrics, }, iterations, optimalBlocking, recommendations, }; }

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