Skip to main content
Glama

runAccessibilityAudit

Audit webpage accessibility to identify and address compliance issues for improved user experience.

Instructions

Run an accessibility audit on the current page

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault

No arguments

Implementation Reference

  • Core handler function that runs Lighthouse accessibility audit and extracts AI-optimized data
    export async function runAccessibilityAudit(
      url: string
    ): Promise<AIOptimizedAccessibilityReport> {
      try {
        const lhr = await runLighthouseAudit(url, [AuditCategory.ACCESSIBILITY]);
        return extractAIOptimizedData(lhr, url);
      } catch (error) {
        throw new Error(
          `Accessibility audit failed: ${
            error instanceof Error ? error.message : String(error)
          }`
        );
      }
    }
  • Supporting handler function that processes raw Lighthouse results into structured AI-optimized accessibility report with issues, scores, and recommendations
    const extractAIOptimizedData = (
      lhr: LighthouseResult,
      url: string
    ): AIOptimizedAccessibilityReport => {
      const categoryData = lhr.categories[AuditCategory.ACCESSIBILITY];
      const audits = lhr.audits || {};
    
      // Add metadata
      const metadata = {
        url,
        timestamp: lhr.fetchTime || new Date().toISOString(),
        device: "desktop", // This could be made configurable
        lighthouseVersion: lhr.lighthouseVersion,
      };
    
      // Initialize variables
      const issues: AIAccessibilityIssue[] = [];
      const criticalElements: AIAccessibilityElement[] = [];
      const categories: {
        [category: string]: { score: number; issues_count: number };
      } = {};
    
      // Count audits by type
      let failedCount = 0;
      let passedCount = 0;
      let manualCount = 0;
      let informativeCount = 0;
      let notApplicableCount = 0;
    
      // Process audit refs
      const auditRefs = categoryData?.auditRefs || [];
    
      // First pass: count audits by type and initialize categories
      auditRefs.forEach((ref) => {
        const audit = audits[ref.id];
        if (!audit) return;
    
        // Count by scoreDisplayMode
        if (audit.scoreDisplayMode === "manual") {
          manualCount++;
        } else if (audit.scoreDisplayMode === "informative") {
          informativeCount++;
        } else if (audit.scoreDisplayMode === "notApplicable") {
          notApplicableCount++;
        } else if (audit.score !== null) {
          // Binary pass/fail
          if (audit.score >= 0.9) {
            passedCount++;
          } else {
            failedCount++;
          }
        }
    
        // Process categories
        if (ref.group) {
          // Initialize category if not exists
          if (!categories[ref.group]) {
            categories[ref.group] = { score: 0, issues_count: 0 };
          }
    
          // Update category score and issues count
          if (audit.score !== null && audit.score < 0.9) {
            categories[ref.group].issues_count++;
          }
        }
      });
    
      // Second pass: process failed audits into AI-friendly format
      auditRefs
        .filter((ref) => {
          const audit = audits[ref.id];
          return audit && audit.score !== null && audit.score < 0.9;
        })
        .sort((a, b) => (b.weight || 0) - (a.weight || 0))
        // No limit on number of failed audits - we'll show them all
        .forEach((ref) => {
          const audit = audits[ref.id];
    
          // Determine impact level based on score and weight
          let impact: "critical" | "serious" | "moderate" | "minor" = "moderate";
          if (audit.score === 0) {
            impact = "critical";
          } else if (audit.score !== null && audit.score <= 0.5) {
            impact = "serious";
          } else if (audit.score !== null && audit.score > 0.7) {
            impact = "minor";
          }
    
          // Create elements array
          const elements: AIAccessibilityElement[] = [];
    
          if (audit.details) {
            const details = audit.details as any;
            if (details.items && Array.isArray(details.items)) {
              const items = details.items;
              // Apply limits based on impact level
              const itemLimit = DETAIL_LIMITS[impact];
              items.slice(0, itemLimit).forEach((item: any) => {
                if (item.node) {
                  const element: AIAccessibilityElement = {
                    selector: item.node.selector,
                    snippet: item.node.snippet,
                    label: item.node.nodeLabel,
                    issue_description: item.node.explanation || item.explanation,
                  };
    
                  if (item.value !== undefined) {
                    element.value = item.value;
                  }
    
                  elements.push(element);
    
                  // Add to critical elements if impact is critical or serious
                  if (impact === "critical" || impact === "serious") {
                    criticalElements.push(element);
                  }
                }
              });
            }
          }
    
          // Create the issue
          const issue: AIAccessibilityIssue = {
            id: ref.id,
            title: audit.title,
            impact,
            category: ref.group || "other",
            elements: elements.length > 0 ? elements : undefined,
            score: audit.score,
          };
    
          issues.push(issue);
        });
    
      // Calculate overall score
      const score = Math.round((categoryData?.score || 0) * 100);
    
      // Generate prioritized recommendations
      const prioritized_recommendations: string[] = [];
    
      // Add category-specific recommendations
      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 "a11y-color-contrast":
              recommendation = "Improve color contrast for better readability";
              break;
            case "a11y-names-labels":
              recommendation = "Add proper labels to all interactive elements";
              break;
            case "a11y-aria":
              recommendation = "Fix ARIA attributes and roles";
              break;
            case "a11y-navigation":
              recommendation = "Improve keyboard navigation and focus management";
              break;
            case "a11y-language":
              recommendation = "Add proper language attributes to HTML";
              break;
            case "a11y-tables-lists":
              recommendation = "Fix table and list structures for screen readers";
              break;
            default:
              recommendation = `Fix ${data.issues_count} issues in ${category}`;
          }
    
          prioritized_recommendations.push(recommendation);
        });
    
      // Add specific high-impact recommendations
      if (issues.some((issue) => issue.id === "color-contrast")) {
        prioritized_recommendations.push(
          "Fix low contrast text for better readability"
        );
      }
    
      if (issues.some((issue) => issue.id === "document-title")) {
        prioritized_recommendations.push("Add a descriptive page title");
      }
    
      if (issues.some((issue) => issue.id === "image-alt")) {
        prioritized_recommendations.push("Add alt text to all images");
      }
    
      // Create the report content
      const reportContent: AccessibilityReportContent = {
        score,
        audit_counts: {
          failed: failedCount,
          passed: passedCount,
          manual: manualCount,
          informative: informativeCount,
          not_applicable: notApplicableCount,
        },
        issues,
        categories,
        critical_elements: criticalElements,
        prioritized_recommendations:
          prioritized_recommendations.length > 0
            ? prioritized_recommendations
            : undefined,
      };
    
      // Return the full report following the LighthouseReport interface
      return {
        metadata,
        report: reportContent,
      };
    };
  • MCP tool registration and proxy handler that forwards to browser-tools-server /accessibility-audit endpoint
    server.tool(
      "runAccessibilityAudit",
      "Run an accessibility audit on the current page",
      {},
      async () => {
        return await withServerConnection(async () => {
          try {
            // Simplified approach - let the browser connector handle the current tab and URL
            console.log(
              `Sending POST request to http://${discoveredHost}:${discoveredPort}/accessibility-audit`
            );
            const response = await fetch(
              `http://${discoveredHost}:${discoveredPort}/accessibility-audit`,
              {
                method: "POST",
                headers: {
                  "Content-Type": "application/json",
                  Accept: "application/json",
                },
                body: JSON.stringify({
                  category: AuditCategory.ACCESSIBILITY,
                  source: "mcp_tool",
                  timestamp: Date.now(),
                }),
              }
            );
    
            // Log the response status
            console.log(`Accessibility audit response status: ${response.status}`);
    
            if (!response.ok) {
              const errorText = await response.text();
              console.error(`Accessibility audit error: ${errorText}`);
              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 accessibility audit:", errorMessage);
            return {
              content: [
                {
                  type: "text",
                  text: `Failed to run accessibility audit: ${errorMessage}`,
                },
              ],
            };
          }
        });
      }
    );
  • HTTP endpoint registration for /accessibility-audit that invokes the lighthouse runAccessibilityAudit function
    private setupAccessibilityAudit() {
      this.setupAuditEndpoint(
        AuditCategory.ACCESSIBILITY,
        "/accessibility-audit",
        runAccessibilityAudit
      );
  • Input/output schema and type definitions for accessibility reports and issues
    export interface AccessibilityReportContent {
      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: AIAccessibilityIssue[];
      categories: {
        [category: string]: {
          score: number;
          issues_count: number;
        };
      };
      critical_elements: AIAccessibilityElement[];
      prioritized_recommendations?: string[]; // Ordered list of recommendations
    }
    
    /**
     * Full accessibility report implementing the base LighthouseReport interface
     */
    export type AIOptimizedAccessibilityReport =
      LighthouseReport<AccessibilityReportContent>;
    
    /**
     * AI-optimized accessibility issue
     */
    interface AIAccessibilityIssue {
      id: string; // e.g., "color-contrast"
      title: string; // e.g., "Color contrast is sufficient"
      impact: "critical" | "serious" | "moderate" | "minor";
      category: string; // e.g., "contrast", "aria", "forms", "keyboard"
      elements?: AIAccessibilityElement[]; // Elements with issues
      score: number | null; // 0-1 or null
    }
    
    /**
     * Accessibility element with issues
     */
    interface AIAccessibilityElement {
      selector: string; // CSS selector
      snippet?: string; // HTML snippet
      label?: string; // Element label
      issue_description?: string; // Description of the issue
      value?: string | number; // Current value (e.g., contrast ratio)
    }

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