Skip to main content
Glama
kbyk004-diy

Playwright-Lighthouse MCP Server

by kbyk004-diy

run-lighthouse

Analyze website performance, accessibility, SEO, and best practices using Lighthouse on the currently open page to identify improvement opportunities.

Instructions

Runs a Lighthouse performance analysis on the currently open page

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
urlYesURL of the website you want to analyze
categoriesNoCategories to analyze (performance, accessibility, best-practices, seo, pwa)
maxItemsNoMaximum number of improvement items to display for each category

Implementation Reference

  • src/index.ts:76-442 (registration)
    Registration of the 'run-lighthouse' tool using McpServer.tool method, including description, input schema, and inline handler function.
    server.tool(
      "run-lighthouse",
      "Runs a Lighthouse performance analysis on the currently open page",
      {
        url: z.string().url().describe("URL of the website you want to analyze"),
        categories: z.array(z.enum(["performance", "accessibility", "best-practices", "seo", "pwa"]))
          .default(["performance"])
          .describe("Categories to analyze (performance, accessibility, best-practices, seo, pwa)"),
        maxItems: z.number().min(1).max(5).default(3)
          .describe("Maximum number of improvement items to display for each category"),
      },
      async (params, extra): Promise<{
        content: { type: "text"; text: string }[];
        isError?: boolean;
      }> => {
        try {
          // Automatically launch browser and navigate to URL
          await navigateToUrl(params.url);
    
          const url = page!.url();
    
          try {
            // CDP connection method for the latest Playwright version
            const browserContext = browser!.contexts()[0];
            const cdpSession = await browserContext.newCDPSession(page!);
            
            // Get browser version information to check debug port
            const versionInfo = await cdpSession.send('Browser.getVersion');
            
            // Get port number from WebSocket debugger URL
            // Note: Using the port specified at launch (9222)
            const port = 9222;
    
            // Function to run Lighthouse audit
            const runAudit = async () => {
              try {
                // Create report path
                const hostname = new URL(url).hostname.replace(/\./g, '-');
                const timestamp = new Date().toISOString().replace(/:/g, '-').split('.')[0];
                const reportPath = path.join(__dirname, `../reports/lighthouse-${hostname}-${timestamp}.json`);
                
                try {
                  // Run Lighthouse audit
                  const results = await playAudit({
                    page: page!,
                    port: port,
                    thresholds: {
                      performance: 0,
                      accessibility: 0,
                      'best-practices': 0,
                      seo: 0,
                      pwa: 0
                    },
                    reports: {
                      formats: {
                        html: false,
                        json: true
                      },
                      directory: path.join(__dirname, "../reports"),
                      name: `lighthouse-${hostname}-${timestamp}`
                    },
                    ignoreError: true,
                    config: {
                      extends: 'lighthouse:default'
                    }
                  });
                  
                  // Function to represent score evaluation with color
                  const getScoreEmoji = (score: number): string => {
                    if (score >= 90) return "🟢"; // Good
                    if (score >= 50) return "🟠"; // Average
                    return "🔴"; // Poor
                  };
    
                  // Process results directly
                  let scoreText = "📊 Lighthouse Scores:\n";
                  let improvementText = "\n\n🔍 Key Improvement Areas:";
                  
                  // Prepare arrays to store improvement items
                  const improvementItems: { category: string; title: string; description: string }[] = [];
                  
                  // Check if results are available directly
                  if (results && results.lhr && results.lhr.categories) {
                    // Get selected categories from the direct results
                    const availableCategories = Object.keys(results.lhr.categories);
                    
                    // Filter categories based on user selection
                    const selectedCategories = params.categories.filter(cat => 
                      availableCategories.includes(cat)
                    );
                    
                    // Process each category
                    for (const category of selectedCategories) {
                      const categoryData = results.lhr.categories[category];
                      
                      if (categoryData) {
                        // Get all audits for this category
                        const audits = results.lhr.audits;
                        const categoryAudits = Object.keys(audits).filter(
                          auditId => {
                            const audit = audits[auditId];
                            return audit.details && 
                                   categoryData.auditRefs.some((ref: any) => ref.id === auditId);
                          }
                        );
                        
                        // Get score
                        let scoreDisplay = '';
                        
                        if (categoryData.score === null) {
                          // When score cannot be calculated
                          scoreDisplay = `⚪️ ${category.charAt(0).toUpperCase() + category.slice(1)}: Not measurable`;
                        } else {
                          // When score can be calculated
                          const score = Math.round(categoryData.score * 100);
                          scoreDisplay = `${getScoreEmoji(score)} ${category.charAt(0).toUpperCase() + category.slice(1)}: ${score}/100`;
                        }
                        
                        // Add score to response
                        scoreText += scoreDisplay + '\n';
                        
                        // Collect improvement items
                        for (const auditId of categoryAudits) {
                          const audit = audits[auditId];
                          if ((audit.score || 0) < 0.9) {
                            improvementItems.push({
                              category,
                              title: audit.title,
                              description: audit.description,
                            });
                          }
                        }
                      }
                    }
                  } else {
                    // Fallback to reading from file if direct results are not available
                    try {
                      // Load JSON file
                      if (existsSync(reportPath)) {
                        // Read and parse JSON file
                        const jsonData = JSON.parse(readFileSync(reportPath, 'utf8'));
                        
                        if (jsonData && jsonData.categories) {
                          // Get selected categories from the report
                          const availableCategories = Object.keys(jsonData.categories);
                          
                          // Filter categories based on user selection
                          const selectedCategories = params.categories.filter(cat => 
                            availableCategories.includes(cat)
                          );
                          
                          // Process each category
                          for (const category of selectedCategories) {
                            const categoryData = jsonData.categories[category];
                            
                            if (categoryData) {
                              // Get all audits for this category
                              const audits = jsonData.audits;
                              const categoryAudits = Object.keys(audits).filter(
                                auditId => {
                                  const audit = audits[auditId];
                                  return audit.details && 
                                         categoryData.auditRefs.some((ref: any) => ref.id === auditId);
                                }
                              );
                              
                              // Get score
                              let scoreDisplay = '';
                              
                              if (categoryData.score === null) {
                                // When score cannot be calculated
                                scoreDisplay = `⚪️ ${category.charAt(0).toUpperCase() + category.slice(1)}: Not measurable`;
                              } else {
                                // When score can be calculated
                                const score = Math.round(categoryData.score * 100);
                                scoreDisplay = `${getScoreEmoji(score)} ${category.charAt(0).toUpperCase() + category.slice(1)}: ${score}/100`;
                              }
                              
                              // Add score to response
                              scoreText += scoreDisplay + '\n';
                              
                              // Collect improvement items
                              for (const auditId of categoryAudits) {
                                const audit = audits[auditId];
                                if ((audit.score || 0) < 0.9) {
                                  improvementItems.push({
                                    category,
                                    title: audit.title,
                                    description: audit.description,
                                  });
                                }
                              }
                            }
                          }
                        }
                      } else {
                        // List all files in directory
                        const files = readdirSync(path.join(__dirname, "../reports"));
                        
                        // Find the latest JSON file
                        const jsonFiles = files.filter(file => file.endsWith('.json'));
                        if (jsonFiles.length > 0) {
                          const latestFile = jsonFiles.sort().pop();
                          
                          // Use the latest file
                          const latestPath = path.join(__dirname, "../reports", latestFile || '');
                          try {
                            const latestData = JSON.parse(readFileSync(latestPath, 'utf8'));
                            
                            if (latestData && latestData.categories) {
                              // Process each category
                              for (const category of params.categories) {
                                const categoryData = latestData.categories[category];
                                
                                if (categoryData) {
                                  // Get all audits for this category
                                  const audits = latestData.audits;
                                  const categoryAudits = Object.keys(audits).filter(
                                    auditId => {
                                      const audit = audits[auditId];
                                      return audit.details && 
                                             categoryData.auditRefs.some((ref: any) => ref.id === auditId);
                                    }
                                  );
                                  
                                  // Get score
                                  let scoreDisplay = '';
                                  
                                  if (categoryData.score === null) {
                                    // When score cannot be calculated
                                    scoreDisplay = `⚪️ ${category.charAt(0).toUpperCase() + category.slice(1)}: Not measurable`;
                                  } else {
                                    // When score can be calculated
                                    const score = Math.round(categoryData.score * 100);
                                    scoreDisplay = `${getScoreEmoji(score)} ${category.charAt(0).toUpperCase() + category.slice(1)}: ${score}/100`;
                                  }
                                  
                                  // Add score to response
                                  scoreText += scoreDisplay + '\n';
                                  
                                  // Collect improvement items
                                  for (const auditId of categoryAudits) {
                                    const audit = audits[auditId];
                                    if ((audit.score || 0) < 0.9) {
                                      improvementItems.push({
                                        category,
                                        title: audit.title,
                                        description: audit.description,
                                      });
                                    }
                                  }
                                }
                              }
                            }
                          } catch (err: any) {
                            throw new Error(`Failed to read latest JSON file: ${err.message}`);
                          }
                        } else {
                          throw new Error('Lighthouse report file not found.');
                        }
                      }
                    } catch (error) {
                      throw error; // Propagate to higher error handler
                    }
                  }
    
                  // Display improvement points (sorted by weight)
                  if (improvementItems.length > 0) {
                    // Sort by category
                    improvementItems.sort((a, b) => {
                      if (a.category !== b.category) {
                        return a.category.localeCompare(b.category);
                      }
                      return a.title.localeCompare(b.title);
                    });
                    
                    // Group and display
                    let currentCategory = '';
                    for (const imp of improvementItems.slice(0, params.maxItems * params.categories.length)) {
                      if (currentCategory !== imp.category) {
                        currentCategory = imp.category;
                        // Display category name appropriately
                        const categoryDisplayName = {
                          'performance': 'Performance',
                          'accessibility': 'Accessibility',
                          'best-practices': 'Best Practices',
                          'seo': 'SEO',
                          'pwa': 'PWA'
                        }[imp.category] || imp.category;
                        
                        improvementText += `\n\n【${categoryDisplayName}】Improvement items:`;
                      }
                      improvementText += `\n・${imp.title}`;
                    }
                  } else {
                    improvementText += "\n\nNo improvement items found.";
                  }
    
                  // Close browser automatically after analysis is complete
                  await closeBrowser();
    
                  // Return the results
                  return {
                    content: [
                      {
                        type: "text" as const,
                        text: scoreText + improvementText,
                      },
                      {
                        type: "text" as const,
                        text: `report save path: ${reportPath}`,
                      },
                    ],
                  };
                } catch (error: any) {
                  // Close browser even when an error occurs
                  await closeBrowser();
                  
                  throw error; // Propagate to higher error handler
                }
              } catch (error: any) {
                // Close browser even when an error occurs
                await closeBrowser();
                
                return {
                  content: [
                    {
                      type: "text" as const,
                      text: `An error occurred during Lighthouse analysis: ${error instanceof Error ? error.message : String(error)}`,
                    },
                  ],
                  isError: true,
                };
              }
            };
    
            return await runAudit();
          } catch (error: any) {
            // Close browser even when an error occurs
            await closeBrowser();
            
            return {
              content: [
                {
                  type: "text" as const,
                  text: `An error occurred during Lighthouse analysis: ${error instanceof Error ? error.message : String(error)}`,
                },
              ],
              isError: true,
            };
          }
        } catch (error: any) {
          // Close browser even when an error occurs
          await closeBrowser();
    
          return {
            content: [
              {
                type: "text" as const,
                text: `An error occurred during Lighthouse analysis: ${error instanceof Error ? error.message : String(error)}`,
              },
            ],
            isError: true,
          };
        }
      }
    );
  • Zod schema defining input parameters for the run-lighthouse tool: url (required), categories (array of enums, default ['performance']), maxItems (number 1-5, default 3).
    {
      url: z.string().url().describe("URL of the website you want to analyze"),
      categories: z.array(z.enum(["performance", "accessibility", "best-practices", "seo", "pwa"]))
        .default(["performance"])
        .describe("Categories to analyze (performance, accessibility, best-practices, seo, pwa)"),
      maxItems: z.number().min(1).max(5).default(3)
        .describe("Maximum number of improvement items to display for each category"),
    },
  • Core handler logic: launches browser/page if needed, navigates to URL, sets up CDP for port 9222, runs playAudit from playwright-lighthouse to generate JSON report, parses Lighthouse results (lhr.categories, audits), computes scores (0-100 with emojis), collects low-score audit improvements limited by maxItems per category, formats text output, saves report to ../reports/, closes browser.
    async (params, extra): Promise<{
      content: { type: "text"; text: string }[];
      isError?: boolean;
    }> => {
      try {
        // Automatically launch browser and navigate to URL
        await navigateToUrl(params.url);
    
        const url = page!.url();
    
        try {
          // CDP connection method for the latest Playwright version
          const browserContext = browser!.contexts()[0];
          const cdpSession = await browserContext.newCDPSession(page!);
          
          // Get browser version information to check debug port
          const versionInfo = await cdpSession.send('Browser.getVersion');
          
          // Get port number from WebSocket debugger URL
          // Note: Using the port specified at launch (9222)
          const port = 9222;
    
          // Function to run Lighthouse audit
          const runAudit = async () => {
            try {
              // Create report path
              const hostname = new URL(url).hostname.replace(/\./g, '-');
              const timestamp = new Date().toISOString().replace(/:/g, '-').split('.')[0];
              const reportPath = path.join(__dirname, `../reports/lighthouse-${hostname}-${timestamp}.json`);
              
              try {
                // Run Lighthouse audit
                const results = await playAudit({
                  page: page!,
                  port: port,
                  thresholds: {
                    performance: 0,
                    accessibility: 0,
                    'best-practices': 0,
                    seo: 0,
                    pwa: 0
                  },
                  reports: {
                    formats: {
                      html: false,
                      json: true
                    },
                    directory: path.join(__dirname, "../reports"),
                    name: `lighthouse-${hostname}-${timestamp}`
                  },
                  ignoreError: true,
                  config: {
                    extends: 'lighthouse:default'
                  }
                });
                
                // Function to represent score evaluation with color
                const getScoreEmoji = (score: number): string => {
                  if (score >= 90) return "🟢"; // Good
                  if (score >= 50) return "🟠"; // Average
                  return "🔴"; // Poor
                };
    
                // Process results directly
                let scoreText = "📊 Lighthouse Scores:\n";
                let improvementText = "\n\n🔍 Key Improvement Areas:";
                
                // Prepare arrays to store improvement items
                const improvementItems: { category: string; title: string; description: string }[] = [];
                
                // Check if results are available directly
                if (results && results.lhr && results.lhr.categories) {
                  // Get selected categories from the direct results
                  const availableCategories = Object.keys(results.lhr.categories);
                  
                  // Filter categories based on user selection
                  const selectedCategories = params.categories.filter(cat => 
                    availableCategories.includes(cat)
                  );
                  
                  // Process each category
                  for (const category of selectedCategories) {
                    const categoryData = results.lhr.categories[category];
                    
                    if (categoryData) {
                      // Get all audits for this category
                      const audits = results.lhr.audits;
                      const categoryAudits = Object.keys(audits).filter(
                        auditId => {
                          const audit = audits[auditId];
                          return audit.details && 
                                 categoryData.auditRefs.some((ref: any) => ref.id === auditId);
                        }
                      );
                      
                      // Get score
                      let scoreDisplay = '';
                      
                      if (categoryData.score === null) {
                        // When score cannot be calculated
                        scoreDisplay = `⚪️ ${category.charAt(0).toUpperCase() + category.slice(1)}: Not measurable`;
                      } else {
                        // When score can be calculated
                        const score = Math.round(categoryData.score * 100);
                        scoreDisplay = `${getScoreEmoji(score)} ${category.charAt(0).toUpperCase() + category.slice(1)}: ${score}/100`;
                      }
                      
                      // Add score to response
                      scoreText += scoreDisplay + '\n';
                      
                      // Collect improvement items
                      for (const auditId of categoryAudits) {
                        const audit = audits[auditId];
                        if ((audit.score || 0) < 0.9) {
                          improvementItems.push({
                            category,
                            title: audit.title,
                            description: audit.description,
                          });
                        }
                      }
                    }
                  }
                } else {
                  // Fallback to reading from file if direct results are not available
                  try {
                    // Load JSON file
                    if (existsSync(reportPath)) {
                      // Read and parse JSON file
                      const jsonData = JSON.parse(readFileSync(reportPath, 'utf8'));
                      
                      if (jsonData && jsonData.categories) {
                        // Get selected categories from the report
                        const availableCategories = Object.keys(jsonData.categories);
                        
                        // Filter categories based on user selection
                        const selectedCategories = params.categories.filter(cat => 
                          availableCategories.includes(cat)
                        );
                        
                        // Process each category
                        for (const category of selectedCategories) {
                          const categoryData = jsonData.categories[category];
                          
                          if (categoryData) {
                            // Get all audits for this category
                            const audits = jsonData.audits;
                            const categoryAudits = Object.keys(audits).filter(
                              auditId => {
                                const audit = audits[auditId];
                                return audit.details && 
                                       categoryData.auditRefs.some((ref: any) => ref.id === auditId);
                              }
                            );
                            
                            // Get score
                            let scoreDisplay = '';
                            
                            if (categoryData.score === null) {
                              // When score cannot be calculated
                              scoreDisplay = `⚪️ ${category.charAt(0).toUpperCase() + category.slice(1)}: Not measurable`;
                            } else {
                              // When score can be calculated
                              const score = Math.round(categoryData.score * 100);
                              scoreDisplay = `${getScoreEmoji(score)} ${category.charAt(0).toUpperCase() + category.slice(1)}: ${score}/100`;
                            }
                            
                            // Add score to response
                            scoreText += scoreDisplay + '\n';
                            
                            // Collect improvement items
                            for (const auditId of categoryAudits) {
                              const audit = audits[auditId];
                              if ((audit.score || 0) < 0.9) {
                                improvementItems.push({
                                  category,
                                  title: audit.title,
                                  description: audit.description,
                                });
                              }
                            }
                          }
                        }
                      }
                    } else {
                      // List all files in directory
                      const files = readdirSync(path.join(__dirname, "../reports"));
                      
                      // Find the latest JSON file
                      const jsonFiles = files.filter(file => file.endsWith('.json'));
                      if (jsonFiles.length > 0) {
                        const latestFile = jsonFiles.sort().pop();
                        
                        // Use the latest file
                        const latestPath = path.join(__dirname, "../reports", latestFile || '');
                        try {
                          const latestData = JSON.parse(readFileSync(latestPath, 'utf8'));
                          
                          if (latestData && latestData.categories) {
                            // Process each category
                            for (const category of params.categories) {
                              const categoryData = latestData.categories[category];
                              
                              if (categoryData) {
                                // Get all audits for this category
                                const audits = latestData.audits;
                                const categoryAudits = Object.keys(audits).filter(
                                  auditId => {
                                    const audit = audits[auditId];
                                    return audit.details && 
                                           categoryData.auditRefs.some((ref: any) => ref.id === auditId);
                                  }
                                );
                                
                                // Get score
                                let scoreDisplay = '';
                                
                                if (categoryData.score === null) {
                                  // When score cannot be calculated
                                  scoreDisplay = `⚪️ ${category.charAt(0).toUpperCase() + category.slice(1)}: Not measurable`;
                                } else {
                                  // When score can be calculated
                                  const score = Math.round(categoryData.score * 100);
                                  scoreDisplay = `${getScoreEmoji(score)} ${category.charAt(0).toUpperCase() + category.slice(1)}: ${score}/100`;
                                }
                                
                                // Add score to response
                                scoreText += scoreDisplay + '\n';
                                
                                // Collect improvement items
                                for (const auditId of categoryAudits) {
                                  const audit = audits[auditId];
                                  if ((audit.score || 0) < 0.9) {
                                    improvementItems.push({
                                      category,
                                      title: audit.title,
                                      description: audit.description,
                                    });
                                  }
                                }
                              }
                            }
                          }
                        } catch (err: any) {
                          throw new Error(`Failed to read latest JSON file: ${err.message}`);
                        }
                      } else {
                        throw new Error('Lighthouse report file not found.');
                      }
                    }
                  } catch (error) {
                    throw error; // Propagate to higher error handler
                  }
                }
    
                // Display improvement points (sorted by weight)
                if (improvementItems.length > 0) {
                  // Sort by category
                  improvementItems.sort((a, b) => {
                    if (a.category !== b.category) {
                      return a.category.localeCompare(b.category);
                    }
                    return a.title.localeCompare(b.title);
                  });
                  
                  // Group and display
                  let currentCategory = '';
                  for (const imp of improvementItems.slice(0, params.maxItems * params.categories.length)) {
                    if (currentCategory !== imp.category) {
                      currentCategory = imp.category;
                      // Display category name appropriately
                      const categoryDisplayName = {
                        'performance': 'Performance',
                        'accessibility': 'Accessibility',
                        'best-practices': 'Best Practices',
                        'seo': 'SEO',
                        'pwa': 'PWA'
                      }[imp.category] || imp.category;
                      
                      improvementText += `\n\n【${categoryDisplayName}】Improvement items:`;
                    }
                    improvementText += `\n・${imp.title}`;
                  }
                } else {
                  improvementText += "\n\nNo improvement items found.";
                }
    
                // Close browser automatically after analysis is complete
                await closeBrowser();
    
                // Return the results
                return {
                  content: [
                    {
                      type: "text" as const,
                      text: scoreText + improvementText,
                    },
                    {
                      type: "text" as const,
                      text: `report save path: ${reportPath}`,
                    },
                  ],
                };
              } catch (error: any) {
                // Close browser even when an error occurs
                await closeBrowser();
                
                throw error; // Propagate to higher error handler
              }
            } catch (error: any) {
              // Close browser even when an error occurs
              await closeBrowser();
              
              return {
                content: [
                  {
                    type: "text" as const,
                    text: `An error occurred during Lighthouse analysis: ${error instanceof Error ? error.message : String(error)}`,
                  },
                ],
                isError: true,
              };
            }
          };
    
          return await runAudit();
        } catch (error: any) {
          // Close browser even when an error occurs
          await closeBrowser();
          
          return {
            content: [
              {
                type: "text" as const,
                text: `An error occurred during Lighthouse analysis: ${error instanceof Error ? error.message : String(error)}`,
              },
            ],
            isError: true,
          };
        }
      } catch (error: any) {
        // Close browser even when an error occurs
        await closeBrowser();
    
        return {
          content: [
            {
              type: "text" as const,
              text: `An error occurred during Lighthouse analysis: ${error instanceof Error ? error.message : String(error)}`,
            },
          ],
          isError: true,
        };
      }
    }
  • Helper function to launch browser/page and navigate to the analysis URL, called first in handler.
    async function navigateToUrl(url: string) {
      try {
        const page = await getPage();
        await page.goto(url, { waitUntil: "load" });
        return page;
      } catch (error) {
        throw error;
      }
    }
  • Helper to launch Chromium browser with remote debugging port 9222 required for Lighthouse via playwright-lighthouse.
    async function launchBrowser() {
      if (!browser) {
        // Launch browser with remote debugging port
        browser = await chromium.launch({
          headless: true,
          args: [
            '--remote-debugging-port=9222',
            '--ignore-certificate-errors'
          ],
          timeout: 30000,
        });
      }
      return browser;
    }
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries full burden for behavioral disclosure. While it states what the tool does, it lacks critical behavioral information: whether this requires network access, how long the analysis might take, what format the results come in, whether it modifies the page, or any error conditions. The description is insufficient for a tool that performs external analysis.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, efficient sentence that states the core functionality without unnecessary words. It's appropriately sized for the tool's purpose and gets straight to the point.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a performance analysis tool with no annotations and no output schema, the description is incomplete. It doesn't explain what kind of results to expect, how they're formatted, whether the analysis is synchronous or asynchronous, or any limitations. The agent would be operating with significant uncertainty about the tool's behavior and outputs.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With 100% schema description coverage, the schema already documents all three parameters thoroughly. The description adds no additional parameter semantics beyond what's in the schema. The baseline score of 3 reflects adequate parameter documentation through the schema alone.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the action ('Runs a Lighthouse performance analysis') and the target ('on the currently open page'), providing a specific verb+resource combination. However, it doesn't explicitly distinguish this tool from its sibling 'take-screenshot' beyond the different functionality implied by the names.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no guidance on when to use this tool versus alternatives, nor does it mention any prerequisites or context for usage. There's no comparison with the sibling 'take-screenshot' tool or indication of when performance analysis versus screenshot capture would be appropriate.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

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/kbyk004-diy/playwright-lighthouse-mcp'

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