Skip to main content
Glama

check_color_contrast

Validate color contrast ratios between foreground and background to ensure compliance with WCAG accessibility standards, supporting various color formats and font attributes.

Instructions

Check if a foreground and background color combination meets WCAG contrast requirements

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
backgroundYesBackground color in various formats (e.g., "#FFFFFF", "#FFF", "rgb(255,255,255)", "hsv(0,0%,100%)")
fontSizeNoFont size in pixels
foregroundYesForeground color in various formats (e.g., "#000000", "#000", "rgb(0,0,0)", "hsv(0,0%,0%)")
isBoldNoWhether the text is bold

Implementation Reference

  • The primary handler function for the 'check_color_contrast' tool. It destructures input arguments, parses foreground and background colors using helper functions, calculates the contrast ratio using the WCAG formula, launches a Puppeteer browser to create a test HTML page with the specified styles, runs Axe specifically on the 'color-contrast' rule, extracts results, determines WCAG AA/AAA compliance based on font size and boldness, and returns a detailed JSON response with normalized colors, ratios, and pass/fail status.
    async checkColorContrast(args: any) {
      const { foreground, background, fontSize = 16, isBold = false } = args;
    
      if (!foreground || !background) {
        throw new McpError(
          ErrorCode.InvalidParams,
          'Missing required parameters: foreground and background colors'
        );
      }
    
      let browser;
      try {
        // Parse colors to RGB values
        let fgRgb, bgRgb;
        
        try {
          fgRgb = this.parseColor(foreground);
          bgRgb = this.parseColor(background);
        } catch (error) {
          throw new McpError(
            ErrorCode.InvalidParams,
            `Color parsing error: ${error instanceof Error ? error.message : String(error)}`
          );
        }
        
        // Convert to hex for Axe engine (as that's what it uses internally)
        const fgHex = this.rgbToHex(fgRgb);
        const bgHex = this.rgbToHex(bgRgb);
        
        // Calculate contrast ratio directly
        const directContrastRatio = this.calculateContrastRatio(fgRgb, bgRgb);
    
        browser = await puppeteer.launch({
          headless: true,
          args: ['--no-sandbox', '--disable-setuid-sandbox']
        });
        
        const page = await browser.newPage();
        
        // Create a simple HTML page with the specified colors
        const html = `
          <!DOCTYPE html>
          <html>
            <head>
              <style>
                .test-element {
                  color: ${fgHex};
                  background-color: ${bgHex};
                  font-size: ${fontSize}px;
                  font-weight: ${isBold ? 'bold' : 'normal'};
                  padding: 20px;
                }
              </style>
            </head>
            <body>
              <div class="test-element">Test Text</div>
            </body>
          </html>
        `;
        
        await page.setContent(html);
        
        // Run only the color-contrast rule
        const axe = new AxePuppeteer(page)
          .options({
            runOnly: {
              type: 'rule',
              values: ['color-contrast']
            }
          });
        
        const result = await axe.analyze();
        
        // Check if there are any violations
        const passes = result.violations.length === 0;
        
        // Extract contrast ratio from failure summary text
        let contrastRatio: number | null = directContrastRatio; // Use our calculated ratio as default
        let extractionMethod = 'direct-calculation';
        
        if (result.violations.length > 0 && result.violations[0].nodes.length > 0) {
          const failureSummary = result.violations[0].nodes[0].failureSummary || '';
          
          // Extract contrast ratio from failure summary using regex
          const match = failureSummary.match(/contrast ratio of ([0-9.]+)/);
          if (match && match[1]) {
            contrastRatio = parseFloat(match[1]);
            extractionMethod = 'axe-calculation';
          }
          
          // Additional inspection of violation data
          if (contrastRatio === null) {
            // Log all properties of the first node to see if contrast data is available elsewhere
            const node = result.violations[0].nodes[0];
            
            if (node.any && node.any.length > 0) {
              if (node.any[0].data) {
                if (node.any[0].data.contrastRatio) {
                  contrastRatio = node.any[0].data.contrastRatio;
                  extractionMethod = 'axe-violation-data';
                }
              }
            }
          }
        } else if (result.passes.length > 0 && result.passes[0].nodes.length > 0) {
          // Try to extract from pass data
          const node = result.passes[0].nodes[0];
          if (node.any && node.any.length > 0 && node.any[0].data && node.any[0].data.contrastRatio) {
            contrastRatio = node.any[0].data.contrastRatio;
            extractionMethod = 'axe-pass-data';
          }
        }
        
        // Determine required contrast ratios based on font size
        const isLargeText = (fontSize >= 18) || (fontSize >= 14 && isBold);
        const requiredRatioAA = isLargeText ? 3.0 : 4.5;
        const requiredRatioAAA = isLargeText ? 4.5 : 7.0;
        
        return {
          content: [
            {
              type: 'text',
              text: JSON.stringify({
                originalInput: {
                  foreground: foreground,
                  background: background,
                },
                normalizedColors: {
                  foregroundHex: fgHex,
                  backgroundHex: bgHex,
                  foregroundRgb: `rgb(${fgRgb.r}, ${fgRgb.g}, ${fgRgb.b})`,
                  backgroundRgb: `rgb(${bgRgb.r}, ${bgRgb.g}, ${bgRgb.b})`,
                },
                fontSize,
                isBold,
                contrastRatio,
                calculationMethod: extractionMethod,
                isLargeText,
                passesWCAG2AA: contrastRatio !== null ? contrastRatio >= requiredRatioAA : passes,
                requiredRatioForAA: requiredRatioAA,
                requiredRatioForAAA: requiredRatioAAA,
                passesWCAG2AAA: contrastRatio !== null ? contrastRatio >= requiredRatioAAA : null,
                helpUrl: "https://dequeuniversity.com/rules/axe/4.10/color-contrast"
              }, null, 2),
            },
          ],
        };
      } finally {
        if (browser) {
          await browser.close();
        }
      }
    }
  • The input schema (inputSchema) for the 'check_color_contrast' tool, defining properties for foreground and background colors (strings supporting multiple formats), optional fontSize (number, default 16), and isBold (boolean, default false), with foreground and background required.
    inputSchema: {
      type: 'object',
      properties: {
        foreground: {
          type: 'string',
          description: 'Foreground color in various formats (e.g., "#000000", "#000", "rgb(0,0,0)", "hsv(0,0%,0%)")',
        },
        background: {
          type: 'string',
          description: 'Background color in various formats (e.g., "#FFFFFF", "#FFF", "rgb(255,255,255)", "hsv(0,0%,100%)")',
        },
        fontSize: {
          type: 'number',
          description: 'Font size in pixels',
          default: 16
        },
        isBold: {
          type: 'boolean',
          description: 'Whether the text is bold',
          default: false
        }
      },
      required: ['foreground', 'background'],
    },
  • src/index.ts:100-127 (registration)
    Registration of the 'check_color_contrast' tool in the MCP server's listTools response, including the tool name, description, and full inputSchema.
    {
      name: 'check_color_contrast',
      description: 'Check if a foreground and background color combination meets WCAG contrast requirements',
      inputSchema: {
        type: 'object',
        properties: {
          foreground: {
            type: 'string',
            description: 'Foreground color in various formats (e.g., "#000000", "#000", "rgb(0,0,0)", "hsv(0,0%,0%)")',
          },
          background: {
            type: 'string',
            description: 'Background color in various formats (e.g., "#FFFFFF", "#FFF", "rgb(255,255,255)", "hsv(0,0%,100%)")',
          },
          fontSize: {
            type: 'number',
            description: 'Font size in pixels',
            default: 16
          },
          isBold: {
            type: 'boolean',
            description: 'Whether the text is bold',
            default: false
          }
        },
        required: ['foreground', 'background'],
      },
    },
  • src/index.ts:168-169 (registration)
    Dispatch registration in the CallToolRequestSchema handler's switch statement, routing 'check_color_contrast' calls to the checkColorContrast method.
    case 'check_color_contrast':
      return await this.checkColorContrast(request.params.arguments);
  • Core helper function that calculates the WCAG contrast ratio between two RGB colors using the standard luminance formula and relative luminance calculation.
    private calculateContrastRatio(color1: { r: number, g: number, b: number }, color2: { r: number, g: number, b: number }): number {
      // Calculate luminance for a color
      const luminance = (rgb: { r: number, g: number, b: number }) => {
        const a = [rgb.r, rgb.g, rgb.b].map(v => {
          v /= 255;
          return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
        });
        return a[0] * 0.2126 + a[1] * 0.7152 + a[2] * 0.0722;
      };
      
      const l1 = luminance(color1);
      const l2 = luminance(color2);
      
      // Calculate contrast ratio
      const ratio = (Math.max(l1, l2) + 0.05) / (Math.min(l1, l2) + 0.05);
      
      return parseFloat(ratio.toFixed(2));
    }
Install Server

Other Tools

Related 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/ronantakizawa/a11ymcp'

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