Skip to main content
Glama

lighthouse-mcp

index.ts8.8 kB
#!/usr/bin/env node import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, CallToolRequest, } from '@modelcontextprotocol/sdk/types.js'; import lighthouse from 'lighthouse'; import * as chromeLauncher from 'chrome-launcher'; // Define types for Lighthouse interface LighthouseResult { lhr: { finalDisplayedUrl: string; fetchTime: string; lighthouseVersion: string; userAgent: string; categories: Record<string, any>; audits: Record<string, any>; }; } interface RunAuditArgs { url: string; categories?: string[]; device?: 'mobile' | 'desktop'; throttling?: boolean; } const isValidAuditArgs = (args: any): args is RunAuditArgs => { return ( typeof args === 'object' && args !== null && typeof args.url === 'string' && (args.categories === undefined || (Array.isArray(args.categories) && args.categories.every((cat: any) => typeof cat === 'string'))) && (args.device === undefined || args.device === 'mobile' || args.device === 'desktop') && (args.throttling === undefined || typeof args.throttling === 'boolean') ); }; class LighthouseServer { private server: Server; constructor() { this.server = new Server( { name: 'lighthouse-mcp', version: '0.1.0', }, { capabilities: { tools: {}, }, } ); this.setupToolHandlers(); // Error handling this.server.onerror = (error: Error) => console.error('[MCP Error]', error); process.on('SIGINT', async () => { await this.server.close(); process.exit(0); }); } private setupToolHandlers() { this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [ { name: 'run_audit', description: 'Run a Lighthouse audit on a URL', inputSchema: { type: 'object', properties: { url: { type: 'string', description: 'URL to audit', }, categories: { type: 'array', items: { type: 'string', enum: [ 'performance', 'accessibility', 'best-practices', 'seo', 'pwa', ], }, description: 'Categories to audit (defaults to all)', }, device: { type: 'string', enum: ['mobile', 'desktop'], description: 'Device to emulate (defaults to mobile)', }, throttling: { type: 'boolean', description: 'Whether to apply network throttling (defaults to true)', }, }, required: ['url'], }, }, { name: 'get_performance_score', description: 'Get just the performance score for a URL', inputSchema: { type: 'object', properties: { url: { type: 'string', description: 'URL to audit', }, device: { type: 'string', enum: ['mobile', 'desktop'], description: 'Device to emulate (defaults to mobile)', }, }, required: ['url'], }, }, ], })); this.server.setRequestHandler(CallToolRequestSchema, async (request: CallToolRequest) => { switch (request.params.name) { case 'run_audit': return this.handleRunAudit(request.params.arguments); case 'get_performance_score': return this.handleGetPerformanceScore(request.params.arguments); default: throw new McpError( ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}` ); } }); } private async handleRunAudit(args: any) { if (!isValidAuditArgs(args)) { throw new McpError( ErrorCode.InvalidParams, 'Invalid audit arguments' ); } try { const chrome = await chromeLauncher.launch({ chromeFlags: ['--headless'] }); const options: any = { logLevel: 'info' as const, output: 'json', onlyCategories: args.categories, port: chrome.port, formFactor: args.device || 'mobile', screenEmulation: { mobile: args.device !== 'desktop', width: args.device === 'desktop' ? 1350 : 360, height: args.device === 'desktop' ? 940 : 640, deviceScaleFactor: 1, disabled: false, }, throttling: args.throttling !== false ? { rttMs: 150, throughputKbps: 1638.4, cpuSlowdownMultiplier: 4, } : { rttMs: 0, throughputKbps: 10 * 1024, cpuSlowdownMultiplier: 1, }, }; const runnerResult = await lighthouse(args.url, options) as LighthouseResult; await chrome.kill(); if (!runnerResult) { throw new McpError( ErrorCode.InternalError, 'Failed to run Lighthouse audit' ); } const { lhr } = runnerResult; // Format the results const formattedResults = { url: lhr.finalDisplayedUrl, fetchTime: lhr.fetchTime, version: lhr.lighthouseVersion, userAgent: lhr.userAgent, scores: {}, metrics: {}, }; // Add category scores const scores: Record<string, any> = {}; for (const [key, category] of Object.entries(lhr.categories as Record<string, any>)) { scores[key] = { title: category.title, score: category.score, description: category.description, }; } formattedResults.scores = scores; // Add key metrics const metrics: Record<string, any> = {}; if (lhr.audits) { const keyMetrics = [ 'first-contentful-paint', 'largest-contentful-paint', 'total-blocking-time', 'cumulative-layout-shift', 'speed-index', 'interactive', ]; for (const metric of keyMetrics) { const audit = (lhr.audits as Record<string, any>)[metric]; if (audit) { metrics[metric] = { title: audit.title, value: audit.numericValue, displayValue: audit.displayValue, score: audit.score, }; } } } formattedResults.metrics = metrics; return { content: [ { type: 'text', text: JSON.stringify(formattedResults, null, 2), }, ], }; } catch (error: any) { console.error('Lighthouse error:', error); return { content: [ { type: 'text', text: `Error running Lighthouse audit: ${error.message || error}`, }, ], isError: true, }; } } private async handleGetPerformanceScore(args: any) { if (!isValidAuditArgs(args)) { throw new McpError( ErrorCode.InvalidParams, 'Invalid performance score arguments' ); } try { // Run a focused performance audit const auditArgs: RunAuditArgs = { url: args.url, categories: ['performance'], device: args.device || 'mobile', throttling: true, }; const result = await this.handleRunAudit(auditArgs); // Extract just the performance data const resultData = JSON.parse(result.content[0].text); const performanceData = { url: resultData.url, performanceScore: resultData.scores.performance.score, metrics: resultData.metrics, }; return { content: [ { type: 'text', text: JSON.stringify(performanceData, null, 2), }, ], }; } catch (error: any) { console.error('Performance score error:', error); return { content: [ { type: 'text', text: `Error getting performance score: ${error.message || error}`, }, ], isError: true, }; } } async run() { const transport = new StdioServerTransport(); await this.server.connect(transport); console.error('Lighthouse MCP server running on stdio'); } } const server = new LighthouseServer(); server.run().catch(console.error);

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

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