Playwright Accessibility Testing MCP Server
A production-ready Model Context Protocol (MCP) server for comprehensive accessibility testing using Playwright and axe-core. Test web applications for WCAG 2.0/2.1 compliance with ease.
Features
- Comprehensive WCAG Testing: Tests WCAG 2.0/2.1 Level A/AA compliance plus best practices
- Natural Language Testing: Find elements by visible text - no CSS selectors needed
- Auto-Discovery: Automatically discover and test all interactive elements
- Interactive Component Testing: Test across different states (dropdowns, modals, etc.)
- Screenshot Capture: Visual documentation of tested areas
- Predefined Prompts: Quick-start templates for common testing scenarios
- Production-Ready: Modular architecture, TypeScript, comprehensive error handling
Installation
npm install
npm run build
Configuration
Add to your MCP client configuration file:
Claude Desktop (~/Library/Application Support/Claude/claude_desktop_config.json):
{
"mcpServers": {
"playwright-a11y": {
"command": "node",
"args": ["/absolute/path/to/playwright-axe-mcp/dist/server.js"]
}
}
}
Claude Code (.claude/config.json in your project):
{
"mcpServers": {
"playwright-a11y": {
"command": "node",
"args": ["/absolute/path/to/playwright-axe-mcp/dist/server.js"]
}
}
}
Restart your MCP client after configuration.
Quick Start
Using the Predefined Prompt (Easiest!)
The fastest way to test is with the comprehensive-a11y-test prompt:
Use comprehensive-a11y-test prompt:
- url: https://example.com
- block: Rewards account
- steps: open menu, select provider, input value, click apply
Parameters:
url(required): URL to testblock(optional): Section name (e.g., "Navigation", "User Profile")steps(optional): Comma-separated interaction steps
What it does:
- Navigates to the URL
- Runs full accessibility audit
- Tests specified section with auto-discovery or custom steps
- Generates human-readable report with:
- Executive summary
- Critical/serious/moderate/minor issues
- Prioritized fix recommendations
Available Tools
1. a11y_scanUrl
Scan a URL for accessibility violations.
Parameters:
{
url: string; // Required: URL to scan
selector?: string; // Optional: CSS selector to scan specific element
waitForSelector?: string; // Optional: Wait for this element before scanning
captureScreenshot?: boolean; // Optional: Capture screenshot (default: false)
timeout?: number; // Optional: Navigation timeout in ms (default: 30000)
}
Example:
{
"url": "https://example.com",
"selector": "header nav",
"captureScreenshot": true
}
2. a11y_scanInteractiveByText
Test components using natural language - no CSS selectors needed!
Parameters:
{
url: string; // Required: URL to navigate to
containerText: string; // Required: Visible text to find section (e.g., "Rewards")
autoDiscover?: boolean; // Optional: Auto-discover interactions (default: true)
customInteractions?: Array<{
stateName: string; // Name for this interaction
elementText: string; // Visible text of element to interact with
action: "click" | "hover" | "focus";
waitAfter?: number; // Wait time in ms (default: 500)
}>;
captureScreenshots?: boolean; // Optional: Screenshot each state
timeout?: number; // Optional: Navigation timeout in ms
}
Example: Auto-discover everything
{
"url": "https://example.com",
"containerText": "User Menu",
"autoDiscover": true,
"captureScreenshots": true
}
Example: Custom interactions
{
"url": "https://example.com",
"containerText": "Rewards",
"customInteractions": [
{
"stateName": "After opening menu",
"elementText": "View Details",
"action": "click"
}
]
}
Response Format
Scan Result
{
"url": "https://example.com",
"timestamp": "2025-01-28T...",
"summary": {
"violations": 3,
"passes": 12,
"incomplete": 1
},
"violations": [
{
"id": "color-contrast",
"impact": "serious",
"description": "Elements must have sufficient color contrast",
"help": "Ensure contrast ratio is at least 4.5:1",
"helpUrl": "https://dequeuniversity.com/rules/axe/4.4/color-contrast",
"tags": ["wcag2aa", "wcag21aa"],
"nodes": [
{
"html": "<button class=\"btn\">Submit</button>",
"target": [".btn"],
"failureSummary": "..."
}
]
}
],
"screenshotPath": "/path/to/screenshot.png"
}
Interactive Scan Result
{
"url": "https://example.com",
"containerText": "Rewards",
"totalStates": 3,
"states": [
{
"stateName": "Initial State",
"summary": {"violations": 0, "passes": 5, "incomplete": 0},
"violations": []
},
{
"stateName": "After interacting with buttons: \"View Details\"",
"action": "click",
"elementText": "View Details",
"summary": {"violations": 2, "passes": 8, "incomplete": 1},
"violations": [...]
}
]
}
Project Structure
server/
├── constants/ # Configuration and constants
│ ├── config.ts # Application configuration
│ ├── selectors.ts # Interactive element selectors
│ └── wcag-tags.ts # WCAG compliance tags
├── types/ # TypeScript type definitions
│ └── scan-result.ts # Scan result interfaces
├── utils/ # Utility functions
│ ├── axe-scanner.ts # Axe configuration and scanning
│ ├── browser.ts # Browser management
│ ├── container-finder.ts # Find elements by text
│ └── screenshot.ts # Screenshot utilities
├── tools/ # Tool implementations
│ ├── scan-url.ts # URL scanning tool
│ └── scan-interactive-by-text.ts # Interactive testing tool
├── prompts/ # Prompt templates
│ └── comprehensive-a11y-test.ts # Predefined test prompt
└── server.ts # Main MCP server entry point
WCAG Compliance
This server tests for:
- WCAG 2.0 Level A (
wcag2a) - WCAG 2.0 Level AA (
wcag2aa) - WCAG 2.1 Level A (
wcag21a) - WCAG 2.1 Level AA (
wcag21aa) - Best Practices (
best-practice)
Impact Levels
Violations are categorized by severity:
- Critical: Blocks access completely - fix immediately
- Serious: Creates significant barriers - fix before release
- Moderate: Noticeable issues - fix soon
- Minor: Small improvements - can be backlogged
Output Files
All screenshots are saved to: ./accessibility-screenshots/
scan-{timestamp}.png- Full page screenshots{name}-{timestamp}.png- Named screenshots
Usage Examples
Test a simple page
User: "Check accessibility of https://example.com"
Claude: → Calls a11y_scanUrl
→ Returns: 3 violations (2 serious, 1 moderate)
Test specific section by name
User: "Test the Navigation menu on https://example.com"
Claude: → Calls a11y_scanInteractiveByText with containerText="Navigation"
→ Auto-discovers all interactive elements
→ Returns: Issues found in different states
Test with custom interactions
User: "Test the Rewards section - click 'View Details' then 'Redeem'"
Claude: → Parses your request
→ Calls a11y_scanInteractiveByText with custom interactions
→ Returns: Accessibility analysis for each state
Development
# Build TypeScript
npm run build
# Watch mode for development
npm run watch
# Start server directly (for testing)
npm start
Adding New Tools
- Create tool file in
server/tools/ - Define schema and execution function
- Register in
server/server.ts
Adding New Utilities
- Create utility file in
server/utils/ - Export functions
- Import where needed
Best Practices
-
Always test with screenshots during initial development:
{"captureScreenshot": true} -
Wait for dynamic content on SPAs:
{"waitForSelector": ".content-loaded"} -
Test interactive components in different states:
- Initial state
- After user interaction
- Error states
- Success states
-
Use natural language testing when you don't know selectors:
{"containerText": "User Menu", "autoDiscover": true}
Troubleshooting
Server not starting
npm run build
# Check config path is absolute
# Restart MCP client
Element not found
- Verify the text exists on the page
- Try a shorter, more specific text
- Check if content is loaded (use
waitForSelector)
Timeout errors
{"timeout": 60000} // Increase to 60 seconds
Requirements
- Node.js 18+
- Playwright (browsers installed automatically)
License
ISC
Contributing
- Follow the modular architecture
- Add TypeScript types for new features
- Document new tools in README
- Update constants instead of hardcoding values
Built for production use with modular, maintainable architecture.
