mcp-accessibility-scanner 1.0.7 → 1.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/Readme.md +141 -137
- package/build/accessibilityChecker.js +62 -64
- package/build/index.js +34 -15
- package/build/server.js +10 -10
- package/package.json +54 -53
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2025 JustasM
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 JustasM
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/Readme.md
CHANGED
|
@@ -1,137 +1,141 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
npm
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
- `
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
- `
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
├──
|
|
130
|
-
├──
|
|
131
|
-
└──
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
1
|
+
[](https://mseep.ai/app/justasmonkev-mcp-accessibility-scanner)
|
|
2
|
+
|
|
3
|
+
# MCP Accessibility Scanner 🔍
|
|
4
|
+
|
|
5
|
+
A Model Context Protocol (MCP) server that provides automated web accessibility scanning using Playwright and Axe-core. This server enables LLMs to perform WCAG compliance checks, capture annotated screenshots, and generate detailed accessibility reports.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
✅ Full WCAG 2.1/2.2 compliance checking
|
|
10
|
+
🖼️ Automatic screenshot capture with violation highlighting
|
|
11
|
+
📄 Detailed JSON reports with remediation guidance
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
You can install the package using any of these methods:
|
|
16
|
+
|
|
17
|
+
Using npm:
|
|
18
|
+
```bash
|
|
19
|
+
npm install -g mcp-accessibility-scanner
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Docker Installation
|
|
23
|
+
|
|
24
|
+
The project includes a Dockerfile that sets up all necessary dependencies including Node.js v22 and Python 3.13.
|
|
25
|
+
|
|
26
|
+
1. Build the Docker image:
|
|
27
|
+
```bash
|
|
28
|
+
docker build -t mcp-server .
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
2. Run the container:
|
|
32
|
+
```bash
|
|
33
|
+
docker run -it -e MCP_PROXY_DEBUG=true mcp-server
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
You can also run it in the background:
|
|
37
|
+
```bash
|
|
38
|
+
docker run -d -p 3000:3000 mcp-server
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Installation in VS Code
|
|
42
|
+
|
|
43
|
+
Install the Accessibility Scanner in VS Code using the VS Code CLI:
|
|
44
|
+
|
|
45
|
+
For VS Code:
|
|
46
|
+
```bash
|
|
47
|
+
code --add-mcp '{"name":"accessibility-scanner","command":"npx","args":["mcp-accessibility-scanner"]}'
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
For VS Code Insiders:
|
|
51
|
+
```bash
|
|
52
|
+
code-insiders --add-mcp '{"name":"accessibility-scanner","command":"npx","args":["mcp-accessibility-scanner"]}'
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Configuration
|
|
56
|
+
|
|
57
|
+
Here's the Claude Desktop configuration:
|
|
58
|
+
|
|
59
|
+
```json
|
|
60
|
+
{
|
|
61
|
+
"mcpServers": {
|
|
62
|
+
"accessibility-scanner": {
|
|
63
|
+
"command": "npx",
|
|
64
|
+
"args": ["-y", "mcp-accessibility-scanner"]
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Usage
|
|
71
|
+
|
|
72
|
+
The scanner exposes a single tool `scan_accessibility` that accepts:
|
|
73
|
+
|
|
74
|
+
- `url`: The webpage URL to scan (required)
|
|
75
|
+
- `violationsTag`: Array of accessibility violation tags to check (required)
|
|
76
|
+
- `viewport`: Optional object to customize the viewport size
|
|
77
|
+
- `width`: number (default: 1920)
|
|
78
|
+
- `height`: number (default: 1080)
|
|
79
|
+
- `shouldRunInHeadless`: Optional boolean to control headless mode (default: true)
|
|
80
|
+
|
|
81
|
+
**Note: When running a scan, an annotated screenshot highlighting any accessibility violations will be automatically saved to your downloads folder.**
|
|
82
|
+
|
|
83
|
+
Example usage in Claude:
|
|
84
|
+
```
|
|
85
|
+
Could you scan example.com for accessibility issues related to color contrast?
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Advanced example with custom options:
|
|
89
|
+
```
|
|
90
|
+
Could you scan example.com for accessibility issues with a viewport of 1280x720 and show the browser window?
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Development
|
|
94
|
+
|
|
95
|
+
Clone and set up the project:
|
|
96
|
+
```bash
|
|
97
|
+
git clone https://github.com/JustasMonkev/mcp-accessibility-scanner.git
|
|
98
|
+
cd mcp-accessibility-scanner
|
|
99
|
+
npm install
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Start the TypeScript compiler in watch mode:
|
|
103
|
+
```bash
|
|
104
|
+
npm run watch
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Test the MCP server locally:
|
|
108
|
+
```bash
|
|
109
|
+
npm run inspector
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Docker Development
|
|
113
|
+
|
|
114
|
+
For development using Docker:
|
|
115
|
+
|
|
116
|
+
1. Build the development image:
|
|
117
|
+
```bash
|
|
118
|
+
docker build -t mcp-server-dev .
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
2. Run with volume mounting for live code changes:
|
|
122
|
+
```bash
|
|
123
|
+
docker run -it -v $(pwd):/app -p 3000:3000 -e MCP_PROXY_DEBUG=true mcp-server-dev
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Project Structure
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
├── src/ # Source code
|
|
130
|
+
│ ├── index.ts # MCP server setup and tool definitions
|
|
131
|
+
│ └── scanner.ts # Core scanning functionality
|
|
132
|
+
├── build/ # Compiled JavaScript output
|
|
133
|
+
├── Dockerfile # Docker configuration for containerized setup
|
|
134
|
+
├── package.json # Project configuration and dependencies
|
|
135
|
+
└── tsconfig.json # TypeScript configuration
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## License
|
|
139
|
+
|
|
140
|
+
MIT
|
|
141
|
+
|
|
@@ -17,62 +17,61 @@ const playwright_1 = require("playwright");
|
|
|
17
17
|
const playwright_2 = require("@axe-core/playwright");
|
|
18
18
|
const node_path_1 = __importDefault(require("node:path"));
|
|
19
19
|
const node_os_1 = __importDefault(require("node:os"));
|
|
20
|
-
function scanViolations(url_1,
|
|
21
|
-
return __awaiter(this, arguments, void 0, function* (url,
|
|
20
|
+
function scanViolations(url_1, violationsTags_1) {
|
|
21
|
+
return __awaiter(this, arguments, void 0, function* (url, violationsTags, viewport = { width: 1920, height: 1080 }, shouldRunInHeadless = true) {
|
|
22
22
|
var _a;
|
|
23
23
|
const browser = yield playwright_1.chromium.launch({
|
|
24
24
|
headless: shouldRunInHeadless,
|
|
25
25
|
args: [
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
]
|
|
26
|
+
"--disable-blink-features=AutomationControlled",
|
|
27
|
+
"--disable-dev-shm-usage",
|
|
28
|
+
"--no-sandbox",
|
|
29
|
+
"--disable-setuid-sandbox",
|
|
30
|
+
],
|
|
31
31
|
});
|
|
32
32
|
const context = yield browser.newContext({
|
|
33
33
|
viewport,
|
|
34
|
-
userAgent:
|
|
34
|
+
userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
|
35
35
|
});
|
|
36
36
|
const page = yield context.newPage();
|
|
37
37
|
yield page.goto(url);
|
|
38
38
|
yield page.addStyleTag({
|
|
39
|
-
content: `
|
|
40
|
-
.a11y-violation {
|
|
41
|
-
position: relative !important;
|
|
42
|
-
outline: 4px solid #FF4444 !important;
|
|
43
|
-
margin: 2px !important;
|
|
44
|
-
}
|
|
45
|
-
.violation-number {
|
|
46
|
-
position: absolute !important;
|
|
47
|
-
top: -12px !important;
|
|
48
|
-
left: -12px !important;
|
|
49
|
-
background: #FF4444;
|
|
50
|
-
color: white !important;
|
|
51
|
-
width: 25px;
|
|
52
|
-
height: 25px;
|
|
53
|
-
border-radius: 50%;
|
|
54
|
-
display: flex !important;
|
|
55
|
-
align-items: center;
|
|
56
|
-
justify-content: center;
|
|
57
|
-
font-weight: bold;
|
|
58
|
-
font-size: 14px;
|
|
59
|
-
z-index: 10000;
|
|
60
|
-
}
|
|
61
|
-
.a11y-violation-info {
|
|
62
|
-
position: absolute !important;
|
|
63
|
-
background: #333333 !important;
|
|
64
|
-
color: white !important;
|
|
65
|
-
padding: 12px !important;
|
|
66
|
-
border-radius: 4px !important;
|
|
67
|
-
font-size: 14px !important;
|
|
68
|
-
max-width: 300px !important;
|
|
69
|
-
z-index: 9999 !important;
|
|
70
|
-
box-shadow: 0 2px 10px rgba(0,0,0,0.3);
|
|
71
|
-
}
|
|
72
|
-
|
|
39
|
+
content: `
|
|
40
|
+
.a11y-violation {
|
|
41
|
+
position: relative !important;
|
|
42
|
+
outline: 4px solid #FF4444 !important;
|
|
43
|
+
margin: 2px !important;
|
|
44
|
+
}
|
|
45
|
+
.violation-number {
|
|
46
|
+
position: absolute !important;
|
|
47
|
+
top: -12px !important;
|
|
48
|
+
left: -12px !important;
|
|
49
|
+
background: #FF4444;
|
|
50
|
+
color: white !important;
|
|
51
|
+
width: 25px;
|
|
52
|
+
height: 25px;
|
|
53
|
+
border-radius: 50%;
|
|
54
|
+
display: flex !important;
|
|
55
|
+
align-items: center;
|
|
56
|
+
justify-content: center;
|
|
57
|
+
font-weight: bold;
|
|
58
|
+
font-size: 14px;
|
|
59
|
+
z-index: 10000;
|
|
60
|
+
}
|
|
61
|
+
.a11y-violation-info {
|
|
62
|
+
position: absolute !important;
|
|
63
|
+
background: #333333 !important;
|
|
64
|
+
color: white !important;
|
|
65
|
+
padding: 12px !important;
|
|
66
|
+
border-radius: 4px !important;
|
|
67
|
+
font-size: 14px !important;
|
|
68
|
+
max-width: 300px !important;
|
|
69
|
+
z-index: 9999 !important;
|
|
70
|
+
box-shadow: 0 2px 10px rgba(0,0,0,0.3);
|
|
71
|
+
}
|
|
72
|
+
`,
|
|
73
73
|
});
|
|
74
|
-
const axe = new playwright_2.AxeBuilder({ page })
|
|
75
|
-
.withTags(violationsTag);
|
|
74
|
+
const axe = new playwright_2.AxeBuilder({ page }).withTags(violationsTags);
|
|
76
75
|
const results = yield axe.analyze();
|
|
77
76
|
let violationCounter = 1;
|
|
78
77
|
for (const violation of results.violations) {
|
|
@@ -80,28 +79,28 @@ function scanViolations(url_1, violationsTag_1) {
|
|
|
80
79
|
try {
|
|
81
80
|
const targetSelector = node.target[0];
|
|
82
81
|
const selector = Array.isArray(targetSelector)
|
|
83
|
-
? targetSelector.join(
|
|
82
|
+
? targetSelector.join(" ")
|
|
84
83
|
: targetSelector;
|
|
85
84
|
yield page.evaluate(({ selector, violationData, counter }) => {
|
|
86
85
|
const elements = document.querySelectorAll(selector);
|
|
87
|
-
elements.forEach(element => {
|
|
86
|
+
elements.forEach((element) => {
|
|
88
87
|
// Create number badge directly on the element
|
|
89
|
-
const numberBadge = document.createElement(
|
|
90
|
-
numberBadge.className =
|
|
88
|
+
const numberBadge = document.createElement("div");
|
|
89
|
+
numberBadge.className = "violation-number";
|
|
91
90
|
numberBadge.textContent = counter.toString();
|
|
92
91
|
// Add violation styling
|
|
93
|
-
element.classList.add(
|
|
92
|
+
element.classList.add("a11y-violation");
|
|
94
93
|
element.appendChild(numberBadge);
|
|
95
94
|
// Create info box
|
|
96
|
-
const listItem = document.createElement(
|
|
97
|
-
listItem.style.marginBottom =
|
|
98
|
-
listItem.innerHTML = `
|
|
99
|
-
<div style="color: #FF4444; font-weight: bold;">
|
|
100
|
-
Violation #${counter}: ${violationData.impact.toUpperCase()}
|
|
101
|
-
</div>
|
|
102
|
-
<div style="margin: 5px 0; font-size: 14px;">
|
|
103
|
-
${violationData.description}
|
|
104
|
-
</div>
|
|
95
|
+
const listItem = document.createElement("div");
|
|
96
|
+
listItem.style.marginBottom = "15px";
|
|
97
|
+
listItem.innerHTML = `
|
|
98
|
+
<div style="color: #FF4444; font-weight: bold;">
|
|
99
|
+
Violation #${counter}: ${violationData.impact.toUpperCase()}
|
|
100
|
+
</div>
|
|
101
|
+
<div style="margin: 5px 0; font-size: 14px;">
|
|
102
|
+
${violationData.description}
|
|
103
|
+
</div>
|
|
105
104
|
`;
|
|
106
105
|
// Position info box relative to element
|
|
107
106
|
document.body.appendChild(listItem);
|
|
@@ -113,9 +112,9 @@ function scanViolations(url_1, violationsTag_1) {
|
|
|
113
112
|
selector: selector,
|
|
114
113
|
violationData: {
|
|
115
114
|
impact: violation.impact,
|
|
116
|
-
description: violation.description
|
|
115
|
+
description: violation.description,
|
|
117
116
|
},
|
|
118
|
-
counter: violationCounter
|
|
117
|
+
counter: violationCounter,
|
|
119
118
|
});
|
|
120
119
|
violationCounter++;
|
|
121
120
|
}
|
|
@@ -133,16 +132,15 @@ function scanViolations(url_1, violationsTag_1) {
|
|
|
133
132
|
element: node.target[0],
|
|
134
133
|
impactLevel: violation.impact,
|
|
135
134
|
description: violation.description,
|
|
136
|
-
wcagCriteria: (_a = violation.tags) === null || _a === void 0 ? void 0 : _a.join(
|
|
135
|
+
wcagCriteria: (_a = violation.tags) === null || _a === void 0 ? void 0 : _a.join(", "),
|
|
137
136
|
});
|
|
138
137
|
}
|
|
139
138
|
}
|
|
140
|
-
const filePath = node_path_1.default.join(node_path_1.default.join(node_os_1.default.homedir(),
|
|
139
|
+
const filePath = node_path_1.default.join(node_path_1.default.join(node_os_1.default.homedir(), "Downloads"), `a11y-report-${Date.now()}.png`);
|
|
141
140
|
const screenshot = yield page.screenshot({
|
|
142
141
|
path: filePath,
|
|
143
|
-
fullPage: true,
|
|
144
142
|
});
|
|
145
|
-
const base64Screenshot = screenshot.toString(
|
|
143
|
+
const base64Screenshot = screenshot.toString("base64");
|
|
146
144
|
yield browser.close();
|
|
147
145
|
return { report, base64Screenshot };
|
|
148
146
|
});
|
package/build/index.js
CHANGED
|
@@ -12,33 +12,52 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
12
12
|
const zod_1 = require("zod");
|
|
13
13
|
const accessibilityChecker_1 = require("./accessibilityChecker");
|
|
14
14
|
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
15
|
+
// Create an MCP server instance
|
|
15
16
|
const server = new mcp_js_1.McpServer({
|
|
16
|
-
name: "
|
|
17
|
-
version: "1.0.0"
|
|
17
|
+
name: "AccessibilityTools",
|
|
18
|
+
version: "1.0.0",
|
|
18
19
|
});
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
20
|
+
const tagValues = [
|
|
21
|
+
"wcag2a", "wcag2aa", "wcag2aaa", "wcag21a", "wcag21aa", "wcag21aaa",
|
|
22
|
+
"wcag22a", "wcag22aa", "wcag22aaa", "section508", "cat.aria", "cat.color",
|
|
23
|
+
"cat.forms", "cat.keyboard", "cat.language", "cat.name-role-value",
|
|
24
|
+
"cat.parsing", "cat.semantics", "cat.sensory-and-visual-cues",
|
|
25
|
+
"cat.structure", "cat.tables", "cat.text-alternatives", "cat.time-and-media",
|
|
26
|
+
];
|
|
27
|
+
server.registerTool("accessibility-scan", {
|
|
28
|
+
title: "Accessibility Scan",
|
|
29
|
+
description: "Runs an accessibility scan on a URL and returns a JSON report and a screenshot.",
|
|
30
|
+
inputSchema: zod_1.z.object({
|
|
31
|
+
url: zod_1.z.string().url().describe("The public URL to scan for accessibility violations"),
|
|
32
|
+
violationsTag: zod_1.z
|
|
33
|
+
.array(zod_1.z.enum(tagValues))
|
|
34
|
+
.min(1) // Ensure the array is not empty
|
|
35
|
+
.describe("An array of tags for violation types to check"),
|
|
36
|
+
viewport: zod_1.z
|
|
37
|
+
.object({
|
|
38
|
+
width: zod_1.z.number().default(1920),
|
|
39
|
+
height: zod_1.z.number().default(1080),
|
|
40
|
+
})
|
|
41
|
+
.optional()
|
|
42
|
+
.describe("Optional viewport dimensions for the scan"),
|
|
43
|
+
shouldRunInHeadless: zod_1.z.boolean().default(true).describe("Whether to run the browser in headless mode"),
|
|
44
|
+
}).shape,
|
|
45
|
+
}, (args) => __awaiter(void 0, void 0, void 0, function* () {
|
|
46
|
+
const { url, violationsTag, viewport, shouldRunInHeadless } = args;
|
|
28
47
|
const { report, base64Screenshot } = yield (0, accessibilityChecker_1.scanViolations)(url, violationsTag, viewport, shouldRunInHeadless);
|
|
29
48
|
return {
|
|
30
49
|
content: [
|
|
31
50
|
{
|
|
32
51
|
type: "text",
|
|
33
|
-
text: JSON.stringify(Object.assign({ message:
|
|
52
|
+
text: JSON.stringify(Object.assign({ message: "The image has been saved to your downloads." }, report), null, 2),
|
|
34
53
|
},
|
|
35
54
|
{
|
|
36
55
|
type: "image",
|
|
37
56
|
data: base64Screenshot,
|
|
38
|
-
mimeType: "image/png"
|
|
39
|
-
}
|
|
57
|
+
mimeType: "image/png",
|
|
58
|
+
},
|
|
40
59
|
],
|
|
41
|
-
isError: false
|
|
60
|
+
isError: false,
|
|
42
61
|
};
|
|
43
62
|
}));
|
|
44
63
|
exports.default = server;
|
package/build/server.js
CHANGED
|
@@ -20,23 +20,23 @@ function main() {
|
|
|
20
20
|
const transport = new stdio_js_1.StdioServerTransport();
|
|
21
21
|
yield index_1.default.connect(transport);
|
|
22
22
|
try {
|
|
23
|
-
console.error(
|
|
24
|
-
console.error(
|
|
23
|
+
console.error("Starting MCP Accessibility checker server...");
|
|
24
|
+
console.error("MCP server connected successfully");
|
|
25
25
|
}
|
|
26
26
|
catch (error) {
|
|
27
|
-
console.error(
|
|
27
|
+
console.error("Error starting server:", error);
|
|
28
28
|
if (error instanceof Error) {
|
|
29
|
-
console.error(
|
|
29
|
+
console.error("Error stack:", error.stack);
|
|
30
30
|
}
|
|
31
31
|
process.exit(1);
|
|
32
32
|
}
|
|
33
33
|
// Handle process events
|
|
34
|
-
process.on(
|
|
35
|
-
console.error(
|
|
34
|
+
process.on("disconnect", () => {
|
|
35
|
+
console.error("Process disconnected");
|
|
36
36
|
process.exit(0);
|
|
37
37
|
});
|
|
38
|
-
process.on(
|
|
39
|
-
console.error(
|
|
38
|
+
process.on("uncaughtException", (error) => {
|
|
39
|
+
console.error("Uncaught exception:", error);
|
|
40
40
|
process.exit(1);
|
|
41
41
|
});
|
|
42
42
|
// Keep the process running
|
|
@@ -44,7 +44,7 @@ function main() {
|
|
|
44
44
|
});
|
|
45
45
|
}
|
|
46
46
|
// Start the server
|
|
47
|
-
main().catch(error => {
|
|
48
|
-
console.error(
|
|
47
|
+
main().catch((error) => {
|
|
48
|
+
console.error("Fatal error:", error);
|
|
49
49
|
process.exit(1);
|
|
50
50
|
});
|
package/package.json
CHANGED
|
@@ -1,53 +1,54 @@
|
|
|
1
|
-
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "mcp-accessibility-scanner",
|
|
3
|
+
"version": "1.0.9",
|
|
4
|
+
"description": "A Model Context Protocol (MCP) server for performing automated accessibility scans of web pages using Playwright and Axe-core",
|
|
5
|
+
"main": "build/index.js",
|
|
6
|
+
"types": "build/index.d.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"mcp-accessibility-scanner": "build/server.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"build/**/*",
|
|
12
|
+
"README.md",
|
|
13
|
+
"LICENSE"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755') && require('fs').chmodSync('build/server.js', '755')\"",
|
|
17
|
+
"prepare": "npm run build",
|
|
18
|
+
"watch": "tsc --watch",
|
|
19
|
+
"inspector": "npx @modelcontextprotocol/inspector build/index.js"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@axe-core/playwright": "^4.10.2",
|
|
23
|
+
"@modelcontextprotocol/sdk": "^1.13.0",
|
|
24
|
+
"@playwright/test": "^1.53.1"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@biomejs/biome": "2.0.4",
|
|
28
|
+
"@types/node": "^24.0.3",
|
|
29
|
+
"ts-node": "^10.9.2",
|
|
30
|
+
"typescript": "^5.8.3"
|
|
31
|
+
},
|
|
32
|
+
"keywords": [
|
|
33
|
+
"mcp",
|
|
34
|
+
"accessibility",
|
|
35
|
+
"a11y",
|
|
36
|
+
"wcag",
|
|
37
|
+
"axe-core",
|
|
38
|
+
"playwright",
|
|
39
|
+
"claude",
|
|
40
|
+
"model-context-protocol"
|
|
41
|
+
],
|
|
42
|
+
"author": "",
|
|
43
|
+
"license": "MIT",
|
|
44
|
+
"repository": {
|
|
45
|
+
"type": "git",
|
|
46
|
+
"url": "git+https://github.com/JustasMonkev/mcp-accessibility-scanner.git"
|
|
47
|
+
},
|
|
48
|
+
"engines": {
|
|
49
|
+
"node": ">=16.0.0"
|
|
50
|
+
},
|
|
51
|
+
"publishConfig": {
|
|
52
|
+
"access": "public"
|
|
53
|
+
}
|
|
54
|
+
}
|