Skip to main content
Glama

runBestPracticesAudit

Audit web pages to identify and report best practice violations for improved performance, accessibility, and security compliance.

Instructions

Run a best practices audit on the current page

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault

No arguments

Implementation Reference

  • Core handler function that executes the Lighthouse best practices audit on the given URL and processes results into AI-optimized format using the helper 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)
          }`
        );
      }
    }
    
    /**
     * Extract AI-optimized Best Practices data from Lighthouse results
     */
    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,
        },
      };
    };
  • MCP server tool registration for runBestPracticesAudit, which proxies requests to the browser 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}`,
                },
              ],
            };
          }
        });
      }
    );
  • Generic helper that sets up audit endpoints like /best-practices-audit, which calls the specific audit function (runBestPracticesAudit). Called specifically at line 1325 for best-practices.
    private setupAuditEndpoint(
      auditType: string,
      endpoint: string,
      auditFunction: (url: string) => Promise<LighthouseReport>
    ) {
      // Add server identity validation endpoint
      this.app.get("/.identity", (req, res) => {
        res.json({
          signature: "mcp-browser-connector-24x7",
          version: "1.2.0",
        });
      });
    
      this.app.post(endpoint, async (req: any, res: any) => {
        try {
          console.log(`${auditType} audit request received`);
    
          // Get URL using our helper method
          const url = await this.getUrlForAudit();
    
          if (!url) {
            console.log(`No URL available for ${auditType} audit`);
            return res.status(400).json({
              error: `URL is required for ${auditType} audit. Make sure you navigate to a page in the browser first, and the browser-tool extension tab is open.`,
            });
          }
    
          // If we're using the stored URL (not from request body), log it now
          if (!req.body?.url && url === currentUrl) {
            console.log(`Using stored URL for ${auditType} audit:`, url);
          }
    
          // Check if we're using the default URL
          if (url === "about:blank") {
            console.log(`Cannot run ${auditType} audit on about:blank`);
            return res.status(400).json({
              error: `Cannot run ${auditType} audit on about:blank`,
            });
          }
    
          console.log(`Preparing to run ${auditType} audit for: ${url}`);
    
          // Run the audit using the provided function
          try {
            const result = await auditFunction(url);
    
            console.log(`${auditType} audit completed successfully`);
            // Return the results
            res.json(result);
          } catch (auditError) {
            console.error(`${auditType} audit failed:`, auditError);
            const errorMessage =
              auditError instanceof Error
                ? auditError.message
                : String(auditError);
            res.status(500).json({
              error: `Failed to run ${auditType} audit: ${errorMessage}`,
            });
          }
        } catch (error) {
          console.error(`Error in ${auditType} audit endpoint:`, error);
          const errorMessage =
            error instanceof Error ? error.message : String(error);
          res.status(500).json({
            error: `Error in ${auditType} audit endpoint: ${errorMessage}`,
          });
        }
      });
    }
  • Type definitions for input/output schema: report content structure, AI-optimized issue format, etc.
    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>;
    
    /**
     * AI-optimized Best Practices issue
     */
    interface AIBestPracticesIssue {
      id: string; // e.g., "js-libraries"
      title: string; // e.g., "Detected JavaScript libraries"
      impact: "critical" | "serious" | "moderate" | "minor";
      category: string; // e.g., "security", "trust", "user-experience", "browser-compat"
      details?: {
        name?: string; // Name of the item (e.g., library name, vulnerability)
        version?: string; // Version information if applicable
        value?: string; // Current value or status
        issue?: string; // Description of the issue
      }[];
      score: number | null; // 0-1 or null
    }
  • Server-side registration of the /best-practices-audit endpoint that invokes runBestPracticesAudit.
    private setupBestPracticesAudit() {
      this.setupAuditEndpoint(
        AuditCategory.BEST_PRACTICES,
        "/best-practices-audit",
        runBestPracticesAudit
      );
    }
Behavior2/5

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

With no annotations provided, the description carries the full burden of behavioral disclosure. It states the action but reveals nothing about what the audit entails (e.g., what standards it checks, whether it's destructive, permission requirements, output format, or execution time). For a tool with zero annotation coverage, this is a significant gap in transparency.

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 with zero wasted words. It's front-loaded with the core action and target, making it easy to parse. Every word earns its place by conveying essential information without redundancy.

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?

Given the complexity of an audit tool (which likely produces detailed results), the absence of annotations, no output schema, and a vague description, the description is incomplete. It doesn't explain what 'best practices' means, what the output looks like, or how it differs from other audit tools, leaving the agent with insufficient context for effective use.

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

Parameters4/5

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

The tool has 0 parameters, and schema description coverage is 100% (as there are no parameters to describe). The description doesn't need to add parameter information, so it appropriately avoids discussing parameters. A baseline of 4 is applied since no parameters exist, and the description doesn't introduce unnecessary parameter details.

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 ('run a best practices audit') and target ('on the current page'), providing a specific verb+resource combination. However, it doesn't differentiate this audit tool from sibling audit tools like runAccessibilityAudit, runPerformanceAudit, or runSEOAudit, which would require specifying what type of best practices it covers.

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 minimal context by specifying 'on the current page,' but offers no guidance on when to use this tool versus alternative audit tools (e.g., runAccessibilityAudit), when not to use it, or any prerequisites. This leaves the agent with insufficient information to choose between similar sibling tools.

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/oenius/browser-tools-mcp'

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