Skip to main content
Glama

simulate_colorblind

Visualize webpage appearance for colorblind users by simulating types like protanopia, deuteranopia, or tritanopia. Enhances accessibility testing with actionable insights.

Instructions

Simulate how a webpage looks for colorblind users

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
outputPathNoOptional path to save the screenshot
typeYesType of color blindness to simulate
urlYesURL to capture
userAgentNoOptional user agent string to use for the request

Implementation Reference

  • Main handler function 'handleColorBlindSimulation' that executes the tool: validates args, launches headless Puppeteer browser, loads the webpage, injects JavaScript implementing colorblind simulation via RGB matrix transformation for protanopia/deuteranopia/tritanopia, applies to all elements' text color and background, captures full-page screenshot to output file, returns structured response with path.
    private async handleColorBlindSimulation(request: any) { if (!request.params.arguments || typeof request.params.arguments.url !== 'string' || typeof request.params.arguments.type !== 'string') { throw new McpError( ErrorCode.InvalidParams, 'URL and type parameters are required' ); } const args: SimulateColorblindArgs = { url: request.params.arguments.url, type: request.params.arguments.type as 'protanopia' | 'deuteranopia' | 'tritanopia', outputPath: request.params.arguments.outputPath, userAgent: request.params.arguments.userAgent }; let browser; try { console.error('[Debug] Launching browser...'); browser = await puppeteer.launch({ headless: true, args: [ '--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage', '--disable-accelerated-2d-canvas', '--disable-gpu', '--window-size=1920,1080' ] }); console.error('[Debug] Creating new page...'); const page = await browser.newPage(); await page.setViewport({ width: 1920, height: 1080 }); await page.setUserAgent(args.userAgent || 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'); // Enable request interception for debugging await page.setRequestInterception(true); page.on('request', request => { console.error(`[Debug] Request: ${request.url()}`); request.continue(); }); page.on('response', response => { console.error(`[Debug] Response: ${response.url()} - ${response.status()}`); }); page.on('console', msg => { console.error(`[Page Console] ${msg.text()}`); }); console.error('[Debug] Navigating to URL...'); const urlToUse = args.url.replace(/^(https?:\/\/)?(www\.)?/, 'https://www.'); console.error(`[Debug] Modified URL: ${urlToUse}`); const response = await page.goto(urlToUse, { waitUntil: 'networkidle2', timeout: 120000 // Increased timeout to 2 minutes }); console.error(`[Debug] Page loaded with status: ${response?.status()}`); // Wait for the page to be fully loaded console.error('[Debug] Waiting for page load...'); await page.waitForSelector('body', { timeout: 120000 }); // Give extra time for dynamic content console.error('[Debug] Waiting for dynamic content...'); await new Promise(resolve => setTimeout(resolve, 5000)); // Inject the colorblind simulation code console.error('[Debug] Injecting colorblind simulation...'); await page.evaluate((type) => { // Implementation of colorblind simulation function multiply(a: number[], b: number[]): number[] { return [ a[0] * b[0] + a[1] * b[3] + a[2] * b[6], a[0] * b[1] + a[1] * b[4] + a[2] * b[7], a[0] * b[2] + a[1] * b[5] + a[2] * b[8], ]; } const colorBlindnessMatrices = { protanopia: [ 0.567, 0.433, 0, 0.558, 0.442, 0, 0, 0.242, 0.758 ], deuteranopia: [ 0.625, 0.375, 0, 0.7, 0.3, 0, 0, 0.3, 0.7 ], tritanopia: [ 0.95, 0.05, 0, 0, 0.433, 0.567, 0, 0.475, 0.525 ] }; window.simulate = function(rgb: RGB, type: string): RGB { const matrix = colorBlindnessMatrices[type as keyof typeof colorBlindnessMatrices]; const result = multiply([rgb.r / 255, rgb.g / 255, rgb.b / 255], matrix); return { r: Math.round(result[0] * 255), g: Math.round(result[1] * 255), b: Math.round(result[2] * 255) }; }; function parseColor(color: string): RGB { color = color.toLowerCase().replace(/\s/g, ''); if (color.startsWith('rgba(') || color.startsWith('rgb(')) { const values = color .replace('rgba(', '') .replace('rgb(', '') .replace(')', '') .split(',') .map(Number); return { r: values[0], g: values[1], b: values[2] }; } if (color.startsWith('#')) { const hex = color.replace('#', ''); const r = parseInt(hex.substr(0, 2), 16); const g = parseInt(hex.substr(2, 2), 16); const b = parseInt(hex.substr(4, 2), 16); return { r, g, b }; } return { r: 0, g: 0, b: 0 }; } function rgbToString(rgb: RGB): string { return `rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`; } // Get all elements with background color or color const elements = document.querySelectorAll('*'); elements.forEach(el => { const htmlEl = el as HTMLElement; const styles = window.getComputedStyle(htmlEl); const color = styles.color; const backgroundColor = styles.backgroundColor; if (color !== 'rgba(0, 0, 0, 0)') { const rgbColor = parseColor(color); const simulatedColor = window.simulate(rgbColor, type); htmlEl.style.color = rgbToString(simulatedColor); } if (backgroundColor !== 'rgba(0, 0, 0, 0)') { const rgbBgColor = parseColor(backgroundColor); const simulatedBgColor = window.simulate(rgbBgColor, type); htmlEl.style.backgroundColor = rgbToString(simulatedBgColor); } }); }, args.type); // Wait for the filter to be applied console.error('[Debug] Waiting for filter to apply...'); await new Promise(resolve => setTimeout(resolve, 2000)); // Get output directory from environment variable or use default const outputDir = process.env.MCP_OUTPUT_DIR || './output'; console.error('[Debug] Taking screenshot...'); const outputPath = join(outputDir, args.outputPath || `colorblind_${args.type}.png`); await page.screenshot({ path: outputPath, fullPage: true }); console.error('[Debug] Screenshot saved successfully'); await browser.close(); console.error('[Debug] Browser closed'); return { content: [ { type: 'text', text: JSON.stringify({ url: args.url, type: args.type, outputPath: outputPath, timestamp: new Date().toISOString(), message: `Screenshot saved with ${args.type} simulation` }, null, 2), }, ], }; } catch (error) { console.error('[Debug] Error occurred:', error); if (browser) { try { await browser.close(); console.error('[Debug] Browser closed after error'); } catch (closeError) { console.error('[Debug] Error closing browser:', closeError); } } return { content: [ { type: 'text', text: `Error simulating color blindness: ${error instanceof Error ? error.message : String(error)}`, }, ], isError: true, }; } }
  • TypeScript interface defining the input schema for the simulate_colorblind tool parameters.
    interface SimulateColorblindArgs { url: string; type: 'protanopia' | 'deuteranopia' | 'tritanopia'; outputPath?: string; userAgent?: string; }
  • src/index.ts:130-156 (registration)
    Registration of the 'simulate_colorblind' tool in the ListToolsRequestSchema response, including name, description, and detailed inputSchema.
    { name: 'simulate_colorblind', description: 'Simulate how a webpage looks for colorblind users', inputSchema: { type: 'object', properties: { url: { type: 'string', description: 'URL to capture', }, type: { type: 'string', enum: ['protanopia', 'deuteranopia', 'tritanopia'], description: 'Type of color blindness to simulate', }, outputPath: { type: 'string', description: 'Optional path to save the screenshot', }, userAgent: { type: 'string', description: 'Optional user agent string to use for the request', }, }, required: ['url', 'type'], }, },
  • src/index.ts:163-164 (registration)
    Dispatch/registration point in the CallToolRequestSchema handler that routes requests for 'simulate_colorblind' to the handler method.
    } else if (request.params.name === 'simulate_colorblind') { return this.handleColorBlindSimulation(request);
  • Helper function to parse CSS color strings (rgb, rgba, hex) into RGB object, used in color simulation.
    function parseColor(color: string): RGB { // Remove all spaces and convert to lowercase color = color.toLowerCase().replace(/\s/g, ''); // Handle rgba/rgb format if (color.startsWith('rgba(') || color.startsWith('rgb(')) { const values = color .replace('rgba(', '') .replace('rgb(', '') .replace(')', '') .split(',') .map(Number); return { r: values[0], g: values[1], b: values[2] }; } // Handle hex format if (color.startsWith('#')) { const hex = color.replace('#', ''); const r = parseInt(hex.substr(0, 2), 16); const g = parseInt(hex.substr(2, 2), 16); const b = parseInt(hex.substr(4, 2), 16); return { r, g, b }; } // Default to black if color format is not recognized return { r: 0, g: 0, b: 0 }; }

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/bilhasry-deriv/mcp-web-a11y'

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