A powerful Model Context Protocol (MCP) server that provides automated web accessibility scanning and browser automation using Playwright and Axe-core. This server enables LLMs to perform WCAG compliance checks, interact with web pages, manage persistent browser sessions, and generate detailed accessibility reports with visual annotations.
✅ Full WCAG 2.0/2.1/2.2 compliance checking (A, AA, AAA levels)
📄 Detailed JSON reports with remediation guidance
🎯 Support for specific violation categories (color contrast, ARIA, forms, keyboard navigation, etc.)
🖱️ Click, hover, and drag elements using accessibility snapshots
⌨️ Type text and handle keyboard inputs
🔍 Capture page snapshots to discover all interactive elements
📸 Take screenshots and save PDFs
🎯 Support for both element-based and coordinate-based interactions
📑 Tab management for multi-page workflows
🌐 Monitor console messages and network requests
⏱️ Wait for dynamic content to load
📁 Handle file uploads and browser dialogs
🔄 Navigate through browser history
You can install the package using any of these methods:
Using npm:
npm install -g mcp-accessibility-scannerA pre-built image is available on Docker Hub. The image includes Chromium and is pre-configured for containerized use — no extra flags needed.
Pull from Docker Hub:
docker pull justasmonkev/mcp-accessibility-scanner claude mcp add mcp-accessibility-scanner -s user -- docker run -i --rm justasmonkev/mcp-accessibility-scannerTo persist screenshots and reports on your host, add a volume mount:
claude mcp add mcp-accessibility-scanner -s user \
-- docker run -i --rm -v /tmp/mcp-output:/app/output justasmonkev/mcp-accessibility-scannerWithout the -v mount, output files only exist inside the container and are lost when it exits.
docker compose up -dThe Compose configuration publishes the unauthenticated MCP HTTP transport on 127.0.0.1:8931 only. Do not expose this port to untrusted networks.
docker build -t mcp-accessibility-scanner .npm run test:dockerInstall the Accessibility Scanner in VS Code using the VS Code CLI:
For VS Code:
code --add-mcp '{"name":"accessibility-scanner","command":"npx","args":["mcp-accessibility-scanner"]}'For VS Code Insiders:
code-insiders --add-mcp '{"name":"accessibility-scanner","command":"npx","args":["mcp-accessibility-scanner"]}'The scanner can run in two modes depending on how you use it.
When launched without a subcommand, the process starts an MCP server that communicates over stdio. This is the mode used by MCP clients such as Claude Desktop, VS Code, and Claude Code -- you should never need to run it by hand.
npx mcp-accessibility-scanner # starts the MCP server (stdio)All of the MCP client configuration examples in this README already use this default mode.
For manual terminal use, the interactive subcommand starts a readline REPL where you can call any tool directly:
$ npx mcp-accessibility-scanner interactive
Interactive mode. Type "<tool-name> <json>" to call a tool. Ctrl+D to exit.
> browser_navigate {"url": "https://example.com"}
> scan_page {"violationsTag": ["wcag21aa"]}
> audit_keyboard {"maxTabs": 30}Each line is <tool-name> <json-arguments>. Omit the JSON to pass {}.
Global browser connection flags still apply here, for example npx mcp-accessibility-scanner --headless interactive.
Use --mobile or PLAYWRIGHT_MCP_MOBILE=1 to emulate a generic mobile device (Pixel 10 for Chromium, iPhone 17 for WebKit). It cannot be combined with --device, CDP attach/launch modes, remote browser endpoints, or --extension.
Use --extension to connect through the current Playwright Extension, which must support extension protocol v2.
npx mcp-accessibility-scanner --extensionSet PLAYWRIGHT_MCP_EXTENSION_TOKEN to the token shown by the extension to bypass the connection approval dialog.
When --user-data-dir contains multiple Chrome profiles, the profile with the extension installed is selected automatically, preferring Chrome's last-used profile.
To print every tool name and its description:
npx mcp-accessibility-scanner list-toolsNote: Tool names like
browser_navigateandscan_pageare MCP tool identifiers (and REPL commands in interactive mode). They are not shell subcommands -- you cannot runnpx mcp-accessibility-scanner browser_navigate.
Here's the Claude Desktop configuration:
{
"mcpServers": {
"accessibility-scanner": {
"command": "npx",
"args": ["-y", "mcp-accessibility-scanner"]
}
}
}You can pass a configuration file to customize Playwright behavior:
{
"mcpServers": {
"accessibility-scanner": {
"command": "npx",
"args": ["-y", "mcp-accessibility-scanner", "--config", "/path/to/config.json"]
}
}
}Create a config.json file with the following options:
{
"browser": {
"browserName": "chromium",
"launchOptions": {
"headless": true,
"channel": "chrome"
},
"cdpLaunch": {
"command": "open",
"args": ["-a", "Slack", "--args", "--remote-debugging-port={port}"],
"startupTimeoutMs": 30000
}
},
"timeouts": {
"navigationTimeout": 60000,
"defaultTimeout": 5000,
"settle": 500
},
"network": {
"allowedOrigins": ["example.com", "trusted-site.com"],
"blockedOrigins": ["ads.example.com"]
}
}Available Options:
browser.browserName: Browser to use (chromium,firefox,webkit)browser.launchOptions.headless: Run browser in headless mode (default:trueon Linux without display,falseotherwise)browser.launchOptions.channel: Browser channel (chrome,chrome-beta,msedge, etc.)browser.cdpEndpoint: Attach to an already-running Chromium-family app with CDP enabledbrowser.cdpHeaders: Map of HTTP headers to send with the CDP connect request, e.g.{ "Authorization": "Bearer <token>" }, for endpoints that require header-based authenticationbrowser.cdpTimeout: Maximum time in milliseconds to wait when connecting to the CDP endpoint (default:30000)browser.cdpLaunch: Launch a Chromium-family desktop app with CDP enabled, wait for the endpoint, and manage the child process lifecycle- CDP attach modes preserve the target browser's existing default-context settings instead of applying Playwright's defaults.
timeouts.navigationTimeout: Maximum time for page navigation in milliseconds (default:60000)timeouts.defaultTimeout: Default timeout for Playwright operations in milliseconds (default:5000)timeouts.settle: How long to wait after each action for triggered work to settle before responding (default:500)network.allowedOrigins: List of origins to allow (blocks all others if specified)network.blockedOrigins: List of origins to block
CLI equivalents are also available: --cdp-launch-command, --cdp-launch-args, --cdp-launch-cwd, --cdp-launch-port, --cdp-launch-startup-timeout, --cdp-endpoint, --cdp-header (repeat for multiple headers, e.g. --cdp-header "Authorization: Bearer <token>"), and --cdp-timeout. The CDP headers and timeout can also be set via the PLAYWRIGHT_MCP_CDP_HEADERS (one Name: Value entry per line) and PLAYWRIGHT_MCP_CDP_TIMEOUT environment variables.
Use --timeout-settle or PLAYWRIGHT_MCP_TIMEOUT_SETTLE to override the post-action settle delay.
When the server runs with --port, it sends MCP heartbeat pings for Streamable HTTP sessions. Set PLAYWRIGHT_MCP_PING_TIMEOUT_MS to override the default 5000 ms timeout. Set it to 0 or any negative value to disable heartbeat pings for clients or proxies that do not answer server-initiated pings.
The MCP server provides comprehensive browser automation and accessibility scanning tools:
Performs a comprehensive accessibility scan on the current page using Axe-core.
Parameters:
violationsTag: Array of WCAG/violation tags to check
Supported Violation Tags:
- WCAG standards:
wcag2a,wcag2aa,wcag2aaa,wcag21a,wcag21aa,wcag21aaa,wcag22a,wcag22aa,wcag22aaa - Section 508:
section508 - Categories:
cat.aria,cat.color,cat.forms,cat.keyboard,cat.language,cat.name-role-value,cat.parsing,cat.semantics,cat.sensory-and-visual-cues,cat.structure,cat.tables,cat.text-alternatives,cat.time-and-media
Crawls and scans multiple internal pages, then aggregates violations across the site.
- Default strategy: link-based BFS from the current URL
- Supports
links,nav,sitemap, andprovidedURL strategies - Always writes a JSON report (default filename:
audit-site-{timestamp}.json)
Example flow:
1. Navigate to your site homepage with browser_navigate
2. Run audit_site with maxPages: 25 and maxDepth: 2
3. Review the report path returned by the tool (written to the MCP output directory)
Runs Axe scans on the same page across viewport/media/zoom variants and compares deltas against baseline.
- Default variants: baseline, mobile, desktop, forced-colors, reduced-motion, zoom-200
- Supports custom variants and optional reload between variants
- Always writes a JSON report (default filename:
scan-matrix-{timestamp}.json)
Example flow:
1. Navigate to a page state you want to validate
2. Run scan_page_matrix with defaults (or provide custom variants)
3. Review per-variant deltas and open the generated JSON report path
Audits real keyboard focus behavior by pressing Tab (and optional Shift+Tab) with practical heuristics.
- Checks skip links, focus visibility, focus jumps, and possible focus traps
- Optional issue screenshots (
screenshotOnIssue) - Always writes a JSON report (default filename:
audit-keyboard-{timestamp}.json)
Example flow:
1. Navigate to the target page and let it fully load
2. Run audit_keyboard with maxTabs: 50
3. Review focus findings and open the generated JSON report path
Navigate to a URL.
- Parameters:
url(string) - Non-2xx main-document responses are shown as an
HTTP statusline in page state.
Go back to the previous page.
Set default navigation timeout for existing tabs.
- Parameters:
timeout(in ms; 30000-300000)
Set default operation timeout for existing tabs.
- Parameters:
timeout(in ms; 30000-300000)
Capture accessibility snapshot of the current page (better than screenshot for analysis).
Large data: URL payloads in snapshot output are truncated to their media type prefix.
- Parameters:
compress(optional boolean, default false)- When true, repeated non-interactive ARIA snapshot nodes are collapsed in the rendered response when a repeated structural pattern appears more than 100 times. The first 10 examples of each collapsed pattern are kept.
- Use
browser_evaluate()to retrieve the full uncompressed list when needed.
Search the current page accessibility snapshot without returning the full snapshot.
- Parameters:
text(case-insensitive substring) orregex(regular expression, supports/pattern/flags) - Returns matching snapshot lines with surrounding context, shown under their path from the root of the tree;
...marks truncated off-path context.
Perform click on a web page element.
- Parameters:
element(description),ref(element reference),doubleClick(optional)
Type text into editable element.
- Parameters:
element,ref,text,submit(optional),slowly(optional)
Hover over element on page.
- Parameters:
element,ref
Perform drag and drop between two elements.
- Parameters:
startElement,startRef,endElement,endRef
Select an option in a dropdown.
- Parameters:
element,ref,values(array)
Fill multiple fields with one call.
- Parameters:
fields(array of objects withname,type,ref, andvalue)
Press a key on the keyboard.
- Parameters:
key(e.g., 'ArrowLeft' or 'a')
Evaluate a JavaScript expression on the page, or on a specific element when a ref is provided. The function's return value is serialized back as the result.
- Parameters:
function(e.g.,() => document.titleor(element) => element.textContent),element(optional),ref(optional)
Take a screenshot of the current page.
- Parameters:
filename(optional),type(pngorjpeg),scale(cssordevice, defaultcss),fullPage(optional),element/refpair (for element screenshots) scale: devicecaptures a high-resolution screenshot using device pixels (accounts for the device pixel ratio);scale: csskeeps the image sized in CSS pixels.
Save page as PDF.
- Parameters:
filename(optional, defaults topage-{timestamp}.pdf)
This tool requires --caps pdf in the CLI.
Install the configured browser engine (use when browser executable is missing).
- Parameters: none
Close the page.
Resize the browser window.
- Parameters:
width,height
Manage browser tabs in one tool.
- Parameters:
action(list,new,close,select) and optionalindex(forcloseandselect).
Returns all console messages from the page.
Large data: URL payloads in console messages are truncated to their media type prefix.
Returns all network requests since loading the page.
Large data: URL payloads in request URLs are truncated to their media type prefix.
Wait for text to appear/disappear or time to pass.
- Parameters:
time(optional),text(optional),textGone(optional)
Handle browser dialogs (alerts, confirms, prompts).
- Parameters:
accept(boolean),promptText(optional)
Upload files to the page.
- Parameters:
paths(array of absolute file paths)
Verify an element by ARIA role/name.
- Parameters:
role,accessibleName
Verify text visibility.
- Parameters:
text
Verify list items at a snapshot reference.
- Parameters:
element,ref,items(array)
Verify an element value or checked state.
- Parameters:
type,element,ref,value
These verification tools require --caps verify:
These tools require --caps vision:
Move mouse to specific coordinates.
- Parameters:
element,x,y
Click at specific coordinates.
- Parameters:
element,x,y,button(optional:left/right/middle),clickCount(optional),delay(optional, ms between mouse down and up)
Drag from one coordinate to another.
- Parameters:
element,startX,startY,endX,endY
Coordinate-based tools require element descriptions for permission checks, but the coordinates themselves are used for action targeting.
1. Navigate to example.com using browser_navigate
2. Run scan_page with violationsTag: ["wcag21aa"]
1. Use browser_navigate to go to example.com
2. Run scan_page with violationsTag: ["cat.color"]
1. Navigate to example.com with browser_navigate
2. Take a browser_snapshot to see available elements
3. Click the "Sign In" button using browser_click
4. Type "user@example.com" using browser_type
5. Run scan_page on the login page
6. Take a browser_take_screenshot to capture the final state
1. Navigate to example.com
2. Use browser_snapshot to capture all interactive elements
3. Review console messages with browser_console_messages
4. Check network activity with browser_network_requests
1. Open a new tab with `browser_tabs` and `{"action":"new"}`
2. Navigate to different pages in each tab
3. Switch to a tab with `browser_tabs` and `{"action":"select", "index": 1}`
4. List all tabs with `browser_tabs` and `{"action":"list"}`
1. Navigate to a page
2. Use browser_wait_for to wait for specific text to appear
3. Interact with the dynamically loaded content
Note: Most interaction tools require element references from browser_snapshot. Always capture a snapshot before attempting to interact with page elements.
Clone and set up the project:
git clone https://github.com/JustasMonkev/mcp-accessibility-scanner.git
cd mcp-accessibility-scanner
npm installMIT
