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)); }

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