Skip to main content
Glama

runBestPracticesAudit

Audit web pages for best practices to identify performance, accessibility, and SEO issues that need improvement.

Instructions

Run a best practices audit on the current page

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault

No arguments

Implementation Reference

  • The primary handler function that executes the Lighthouse best practices audit by calling runLighthouseAudit and processing results with extractAIOptimizedData.
    export async function runBestPracticesAudit(
      url: string
    ): Promise<AIOptimizedBestPracticesReport> {
      try {
        const lhr = await runLighthouseAudit(url, [AuditCategory.BEST_PRACTICES]);
        return extractAIOptimizedData(lhr, url);
      } catch (error) {
        throw new Error(
          `Best Practices audit failed: ${
            error instanceof Error ? error.message : String(error)
          }`
        );
      }
    }
  • MCP server registration of the 'runBestPracticesAudit' tool, which proxies requests to the browser-tools-server endpoint /best-practices-audit.
    server.tool(
      "runBestPracticesAudit",
      "Run a best practices audit on the current page",
      {},
      async () => {
        return await withServerConnection(async () => {
          try {
            console.log(
              `Sending POST request to http://${discoveredHost}:${discoveredPort}/best-practices-audit`
            );
            const response = await fetch(
              `http://${discoveredHost}:${discoveredPort}/best-practices-audit`,
              {
                method: "POST",
                headers: {
                  "Content-Type": "application/json",
                  Accept: "application/json",
                },
                body: JSON.stringify({
                  source: "mcp_tool",
                  timestamp: Date.now(),
                }),
              }
            );
    
            // Check for errors
            if (!response.ok) {
              const errorText = await response.text();
              throw new Error(`Server returned ${response.status}: ${errorText}`);
            }
    
            const json = await response.json();
    
            // flatten it by merging metadata with the report contents
            if (json.report) {
              const { metadata, report } = json;
              const flattened = {
                ...metadata,
                ...report,
              };
    
              return {
                content: [
                  {
                    type: "text",
                    text: JSON.stringify(flattened, null, 2),
                  },
                ],
              };
            } else {
              // Return as-is if it's not in the new format
              return {
                content: [
                  {
                    type: "text",
                    text: JSON.stringify(json, null, 2),
                  },
                ],
              };
            }
          } catch (error) {
            const errorMessage =
              error instanceof Error ? error.message : String(error);
            console.error("Error in Best Practices audit:", errorMessage);
            return {
              content: [
                {
                  type: "text",
                  text: `Failed to run Best Practices audit: ${errorMessage}`,
                },
              ],
            };
          }
        });
      }
    );
  • Type definitions for the Best Practices report structure, including content schema and AI-optimized report type.
    export interface BestPracticesReportContent {
      score: number; // Overall score (0-100)
      audit_counts: {
        // Counts of different audit types
        failed: number;
        passed: number;
        manual: number;
        informative: number;
        not_applicable: number;
      };
      issues: AIBestPracticesIssue[];
      categories: {
        [category: string]: {
          score: number;
          issues_count: number;
        };
      };
      prioritized_recommendations?: string[]; // Ordered list of recommendations
    }
    
    /**
     * Full Best Practices report implementing the base LighthouseReport interface
     */
    export type AIOptimizedBestPracticesReport =
      LighthouseReport<BestPracticesReportContent>;
  • Helper function that transforms raw Lighthouse results into the AI-optimized Best Practices report format, including issue categorization, scoring, and recommendations.
    const extractAIOptimizedData = (
      lhr: LighthouseResult,
      url: string
    ): AIOptimizedBestPracticesReport => {
      const categoryData = lhr.categories[AuditCategory.BEST_PRACTICES];
      const audits = lhr.audits || {};
    
      // Add metadata
      const metadata = {
        url,
        timestamp: lhr.fetchTime || new Date().toISOString(),
        device: lhr.configSettings?.formFactor || "desktop",
        lighthouseVersion: lhr.lighthouseVersion || "unknown",
      };
    
      // Process audit results
      const issues: AIBestPracticesIssue[] = [];
      const categories: { [key: string]: { score: number; issues_count: number } } =
        {
          security: { score: 0, issues_count: 0 },
          trust: { score: 0, issues_count: 0 },
          "user-experience": { score: 0, issues_count: 0 },
          "browser-compat": { score: 0, issues_count: 0 },
          other: { score: 0, issues_count: 0 },
        };
    
      // Counters for audit types
      let failedCount = 0;
      let passedCount = 0;
      let manualCount = 0;
      let informativeCount = 0;
      let notApplicableCount = 0;
    
      // Process failed audits (score < 1)
      const failedAudits = Object.entries(audits)
        .filter(([, audit]) => {
          const score = audit.score;
          return (
            score !== null &&
            score < 1 &&
            audit.scoreDisplayMode !== "manual" &&
            audit.scoreDisplayMode !== "notApplicable"
          );
        })
        .map(([auditId, audit]) => ({ auditId, ...audit }));
    
      // Update counters
      Object.values(audits).forEach((audit) => {
        const { score, scoreDisplayMode } = audit;
    
        if (scoreDisplayMode === "manual") {
          manualCount++;
        } else if (scoreDisplayMode === "informative") {
          informativeCount++;
        } else if (scoreDisplayMode === "notApplicable") {
          notApplicableCount++;
        } else if (score === 1) {
          passedCount++;
        } else if (score !== null && score < 1) {
          failedCount++;
        }
      });
    
      // Process failed audits into AI-friendly format
      failedAudits.forEach((ref: any) => {
        // Determine impact level based on audit score and weight
        let impact: "critical" | "serious" | "moderate" | "minor" = "moderate";
        const score = ref.score || 0;
    
        // Use a more reliable approach to determine impact
        if (score === 0) {
          impact = "critical";
        } else if (score < 0.5) {
          impact = "serious";
        } else if (score < 0.9) {
          impact = "moderate";
        } else {
          impact = "minor";
        }
    
        // Categorize the issue
        let category = "other";
    
        // Security-related issues
        if (
          ref.auditId.includes("csp") ||
          ref.auditId.includes("security") ||
          ref.auditId.includes("vulnerab") ||
          ref.auditId.includes("password") ||
          ref.auditId.includes("cert") ||
          ref.auditId.includes("deprecat")
        ) {
          category = "security";
        }
        // Trust and legitimacy issues
        else if (
          ref.auditId.includes("doctype") ||
          ref.auditId.includes("charset") ||
          ref.auditId.includes("legit") ||
          ref.auditId.includes("trust")
        ) {
          category = "trust";
        }
        // User experience issues
        else if (
          ref.auditId.includes("user") ||
          ref.auditId.includes("experience") ||
          ref.auditId.includes("console") ||
          ref.auditId.includes("errors") ||
          ref.auditId.includes("paste")
        ) {
          category = "user-experience";
        }
        // Browser compatibility issues
        else if (
          ref.auditId.includes("compat") ||
          ref.auditId.includes("browser") ||
          ref.auditId.includes("vendor") ||
          ref.auditId.includes("js-lib")
        ) {
          category = "browser-compat";
        }
    
        // Count issues by category
        categories[category].issues_count++;
    
        // Create issue object
        const issue: AIBestPracticesIssue = {
          id: ref.auditId,
          title: ref.title,
          impact,
          category,
          score: ref.score,
          details: [],
        };
    
        // Extract details if available
        const refDetails = ref.details as BestPracticesAuditDetails | undefined;
        if (refDetails?.items && Array.isArray(refDetails.items)) {
          const itemLimit = DETAIL_LIMITS[impact];
          const detailItems = refDetails.items.slice(0, itemLimit);
    
          detailItems.forEach((item: Record<string, unknown>) => {
            issue.details = issue.details || [];
    
            // Different audits have different detail structures
            const detail: Record<string, string> = {};
    
            if (typeof item.name === "string") detail.name = item.name;
            if (typeof item.version === "string") detail.version = item.version;
            if (typeof item.issue === "string") detail.issue = item.issue;
            if (item.value !== undefined) detail.value = String(item.value);
    
            // For JS libraries, extract name and version
            if (
              ref.auditId === "js-libraries" &&
              typeof item.name === "string" &&
              typeof item.version === "string"
            ) {
              detail.name = item.name;
              detail.version = item.version;
            }
    
            // Add other generic properties that might exist
            for (const [key, value] of Object.entries(item)) {
              if (!detail[key] && typeof value === "string") {
                detail[key] = value;
              }
            }
    
            issue.details.push(detail as any);
          });
        }
    
        issues.push(issue);
      });
    
      // Calculate category scores (0-100)
      Object.keys(categories).forEach((category) => {
        // Simplified scoring: if there are issues in this category, score is reduced proportionally
        const issueCount = categories[category].issues_count;
        if (issueCount > 0) {
          // More issues = lower score, max penalty of 25 points per issue
          const penalty = Math.min(100, issueCount * 25);
          categories[category].score = Math.max(0, 100 - penalty);
        } else {
          categories[category].score = 100;
        }
      });
    
      // Generate prioritized recommendations
      const prioritized_recommendations: string[] = [];
    
      // Prioritize recommendations by category with most issues
      Object.entries(categories)
        .filter(([_, data]) => data.issues_count > 0)
        .sort(([_, a], [__, b]) => b.issues_count - a.issues_count)
        .forEach(([category, data]) => {
          let recommendation = "";
    
          switch (category) {
            case "security":
              recommendation = `Address ${data.issues_count} security issues: vulnerabilities, CSP, deprecations`;
              break;
            case "trust":
              recommendation = `Fix ${data.issues_count} trust & legitimacy issues: doctype, charset`;
              break;
            case "user-experience":
              recommendation = `Improve ${data.issues_count} user experience issues: console errors, user interactions`;
              break;
            case "browser-compat":
              recommendation = `Resolve ${data.issues_count} browser compatibility issues: outdated libraries, vendor prefixes`;
              break;
            default:
              recommendation = `Fix ${data.issues_count} other best practice issues`;
          }
    
          prioritized_recommendations.push(recommendation);
        });
    
      // Return the optimized report
      return {
        metadata,
        report: {
          score: categoryData?.score ? Math.round(categoryData.score * 100) : 0,
          audit_counts: {
            failed: failedCount,
            passed: passedCount,
            manual: manualCount,
            informative: informativeCount,
            not_applicable: notApplicableCount,
          },
          issues,
          categories,
          prioritized_recommendations,
        },
      };
    };
  • Core helper function that runs the actual Lighthouse audit using a dedicated headless browser instance.
    export async function runLighthouseAudit(
      url: string,
      categories: string[]
    ): Promise<LighthouseResult> {
      console.log(`Starting Lighthouse ${categories.join(", ")} audit for: ${url}`);
    
      if (!url || url === "about:blank") {
        console.error("Invalid URL for Lighthouse audit");
        throw new Error(
          "Cannot run audit on an empty page or about:blank. Please navigate to a valid URL first."
        );
      }
    
      try {
        // Always use a dedicated headless browser for audits
        console.log("Using dedicated headless browser for audit");
    
        // Determine if this is a performance audit - we need to load all resources for performance audits
        const isPerformanceAudit = categories.includes(AuditCategory.PERFORMANCE);
    
        // For performance audits, we want to load all resources
        // For accessibility or other audits, we can block non-essential resources
        try {
          const { port } = await connectToHeadlessBrowser(url, {
            blockResources: !isPerformanceAudit,
          });
    
          console.log(`Connected to browser on port: ${port}`);
    
          // Create Lighthouse config
          const { flags, config } = createLighthouseConfig(categories);
          flags.port = port;
    
          console.log(
            `Running Lighthouse with categories: ${categories.join(", ")}`
          );
          const runnerResult = await lighthouse(url, flags as Flags, config);
          console.log("Lighthouse scan completed");
    
          if (!runnerResult?.lhr) {
            console.error("Lighthouse audit failed to produce results");
            throw new Error("Lighthouse audit failed to produce results");
          }
    
          // Schedule browser cleanup after a delay to allow for subsequent audits
          scheduleBrowserCleanup();
    
          // Return the result
          const result = runnerResult.lhr;
    
          return result;
        } catch (browserError) {
          // Check if the error is related to Chrome/Edge not being available
          const errorMessage =
            browserError instanceof Error
              ? browserError.message
              : String(browserError);
          if (
            errorMessage.includes("Chrome could not be found") ||
            errorMessage.includes("Failed to launch browser") ||
            errorMessage.includes("spawn ENOENT")
          ) {
            throw new Error(
              "Chrome or Edge browser could not be found. Please ensure that Chrome or Edge is installed on your system to run audits."
            );
          }
          // Re-throw other errors
          throw browserError;
        }
      } catch (error) {
        console.error("Lighthouse audit failed:", error);
        // Schedule browser cleanup even if the audit fails
        scheduleBrowserCleanup();
        throw new Error(
          `Lighthouse audit failed: ${
            error instanceof Error ? error.message : String(error)
          }`
        );
      }
    }

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/Sugatraj/Cursor-Browser-Tools-MCP'

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