arc-templated-issues-mcp 1.0.0
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/README.md +101 -0
- package/data/template-index.json +20 -0
- package/package.json +28 -0
- package/src/index.js +62 -0
- package/src/tools.js +342 -0
package/README.md
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# arc-templated-issues-mcp
|
|
2
|
+
|
|
3
|
+
ARC accessibility issue templates as an MCP server. Converts AxeCore violations into formatted issue templates and provides access to web and native accessibility issue templates.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
Add to your Claude Desktop / Claude Code MCP settings:
|
|
8
|
+
|
|
9
|
+
```json
|
|
10
|
+
{
|
|
11
|
+
"mcpServers": {
|
|
12
|
+
"arc-issues": {
|
|
13
|
+
"command": "npx",
|
|
14
|
+
"args": ["-y", "arc-templated-issues-mcp"]
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Tools
|
|
21
|
+
|
|
22
|
+
| Tool | Description |
|
|
23
|
+
|------|-------------|
|
|
24
|
+
| `list_issue_templates` | Lists available accessibility issue template files (web and native). |
|
|
25
|
+
| `get_issue_template` | Returns the full content of a template by name (`web-issue-templates.md` or `native-issue-templates.md`). |
|
|
26
|
+
| `format_violations` | Converts one or more AxeCore violations into formatted markdown or JSON issue templates. |
|
|
27
|
+
| `validate_issue` | Validates that a formatted issue contains all required sections. |
|
|
28
|
+
|
|
29
|
+
### `format_violations`
|
|
30
|
+
|
|
31
|
+
Accepts standard AxeCore violation objects and returns structured issue templates ready for filing.
|
|
32
|
+
|
|
33
|
+
**Parameters:**
|
|
34
|
+
|
|
35
|
+
| Parameter | Type | Required | Description |
|
|
36
|
+
|-----------|------|----------|-------------|
|
|
37
|
+
| `violations` | object or array | Yes | Single AxeCore violation or array of violations |
|
|
38
|
+
| `context` | object | No | Page context: `url`, `browser`, `operatingSystem`, `buildVersion`, `stepsToReproduce` |
|
|
39
|
+
| `outputFormat` | `"markdown"` \| `"json"` | No | Defaults to `"markdown"` |
|
|
40
|
+
|
|
41
|
+
**Example input:**
|
|
42
|
+
```json
|
|
43
|
+
{
|
|
44
|
+
"violations": [{
|
|
45
|
+
"id": "button-name",
|
|
46
|
+
"impact": "critical",
|
|
47
|
+
"tags": ["wcag412", "wcag244"],
|
|
48
|
+
"description": "Ensures buttons have discernible text",
|
|
49
|
+
"help": "Buttons must have discernible text",
|
|
50
|
+
"helpUrl": "https://dequeuniversity.com/rules/axe/4.10/button-name",
|
|
51
|
+
"nodes": [{
|
|
52
|
+
"html": "<button></button>",
|
|
53
|
+
"target": [".cta-button"],
|
|
54
|
+
"failureSummary": "Element does not have inner text that is visible to screen readers"
|
|
55
|
+
}]
|
|
56
|
+
}],
|
|
57
|
+
"context": { "url": "https://example.com/checkout" }
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Templates
|
|
62
|
+
|
|
63
|
+
Templates are sourced from the [`arc-templated-issues`](https://gitlab.com/tmobile/arc/arc-templated-issues) npm package.
|
|
64
|
+
|
|
65
|
+
| Template | Platform | Contents |
|
|
66
|
+
|----------|----------|----------|
|
|
67
|
+
| `web-issue-templates.md` | Web | Control labeling, heading structure, images, keyboard access, link text, contrast, focus management, semantic HTML |
|
|
68
|
+
| `native-issue-templates.md` | Native (iOS/Android) | Button labels, focus order, heading traits, decorative images, grouping, state communication |
|
|
69
|
+
|
|
70
|
+
## Project Structure
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
src/
|
|
74
|
+
index.js MCP server (stdio transport)
|
|
75
|
+
tools.js Tool definitions and handlers
|
|
76
|
+
data/
|
|
77
|
+
template-index.json Routing — maps template names to paths within arc-templated-issues
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Development
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
git clone <repo-url>
|
|
84
|
+
npm install
|
|
85
|
+
npm start
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## npm scripts
|
|
89
|
+
|
|
90
|
+
| Script | Description |
|
|
91
|
+
|--------|-------------|
|
|
92
|
+
| `npm start` | Start the MCP server |
|
|
93
|
+
| `npm run release:patch` | Bump patch version and publish to npm |
|
|
94
|
+
| `npm run release:minor` | Bump minor version and publish to npm |
|
|
95
|
+
| `npm run release:major` | Bump major version and publish to npm |
|
|
96
|
+
|
|
97
|
+
## Learn More
|
|
98
|
+
|
|
99
|
+
- [Model Context Protocol](https://modelcontextprotocol.io/)
|
|
100
|
+
- [MCP TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk)
|
|
101
|
+
- [ARC Accessibility Issue Templates](https://gitlab.com/tmobile/arc/arc-templated-issues)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "1.0.0",
|
|
3
|
+
"description": "Routes accessibility issue template requests to markdown source files",
|
|
4
|
+
"packageName": "arc-templated-issues",
|
|
5
|
+
"templates": [
|
|
6
|
+
{
|
|
7
|
+
"name": "web-issue-templates.md",
|
|
8
|
+
"platform": "web",
|
|
9
|
+
"path": "public/web-issue-templates.md",
|
|
10
|
+
"description": "Web accessibility issue templates (AxeCore-compatible)"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"name": "native-issue-templates.md",
|
|
14
|
+
"platform": "native",
|
|
15
|
+
"path": "public/native-issue-templates.md",
|
|
16
|
+
"description": "Native iOS/Android accessibility issue templates"
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"defaultTemplate": "web-issue-templates.md"
|
|
20
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "arc-templated-issues-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "ARC accessibility issue templates MCP server",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"arc-templated-issues-mcp": "src/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"src/",
|
|
12
|
+
"data/template-index.json"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"start": "node src/index.js",
|
|
16
|
+
"dev": "node src/index.js",
|
|
17
|
+
"release:patch": "npm version patch && npm publish",
|
|
18
|
+
"release:minor": "npm version minor && npm publish",
|
|
19
|
+
"release:major": "npm version major && npm publish"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@modelcontextprotocol/sdk": "^1.0.4",
|
|
23
|
+
"arc-templated-issues": "^1.0.0"
|
|
24
|
+
},
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=18.0.0"
|
|
27
|
+
}
|
|
28
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
3
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
+
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
5
|
+
import { tools } from './tools.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Create and configure the MCP server for local (stdio) use
|
|
9
|
+
*/
|
|
10
|
+
const server = new Server(
|
|
11
|
+
{
|
|
12
|
+
name: 'arc-templated-issues-mcp',
|
|
13
|
+
version: '1.0.0',
|
|
14
|
+
description: 'ARC accessibility issue templates MCP server — formats AxeCore violations and provides web/native issue templates',
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
capabilities: {
|
|
18
|
+
tools: {},
|
|
19
|
+
},
|
|
20
|
+
}
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Handler for listing available tools
|
|
25
|
+
*/
|
|
26
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
27
|
+
return {
|
|
28
|
+
tools: tools.map(tool => ({
|
|
29
|
+
name: tool.name,
|
|
30
|
+
description: tool.description,
|
|
31
|
+
inputSchema: tool.inputSchema
|
|
32
|
+
}))
|
|
33
|
+
};
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Handler for calling tools
|
|
38
|
+
*/
|
|
39
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
40
|
+
const tool = tools.find(t => t.name === request.params.name);
|
|
41
|
+
|
|
42
|
+
if (!tool) {
|
|
43
|
+
throw new Error(`Unknown tool: ${request.params.name}`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Execute tool handler
|
|
47
|
+
return await tool.handler(request.params.arguments);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Start the server with stdio transport
|
|
52
|
+
*/
|
|
53
|
+
async function main() {
|
|
54
|
+
const transport = new StdioServerTransport();
|
|
55
|
+
await server.connect(transport);
|
|
56
|
+
console.error('arc-templated-issues-mcp MCP server running on stdio');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
main().catch((error) => {
|
|
60
|
+
console.error('Fatal error in main():', error);
|
|
61
|
+
process.exit(1);
|
|
62
|
+
});
|
package/src/tools.js
ADDED
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { resolve, dirname } from 'node:path';
|
|
3
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
4
|
+
import { createRequire } from 'node:module';
|
|
5
|
+
|
|
6
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const DATA_DIR = resolve(__dirname, '../data');
|
|
8
|
+
const require = createRequire(import.meta.url);
|
|
9
|
+
|
|
10
|
+
async function loadIndex() {
|
|
11
|
+
const raw = await readFile(resolve(DATA_DIR, 'template-index.json'), 'utf8');
|
|
12
|
+
return JSON.parse(raw);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function resolvePackageDir(packageName) {
|
|
16
|
+
const pkgJson = require.resolve(`${packageName}/package.json`);
|
|
17
|
+
return dirname(pkgJson);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function readTemplate(entry, packageName) {
|
|
21
|
+
const pkgDir = resolvePackageDir(packageName);
|
|
22
|
+
return readFile(resolve(pkgDir, entry.path), 'utf8');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function textResponse(text) {
|
|
26
|
+
return { content: [{ type: 'text', text }] };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// AxeCore impact → severity/priority
|
|
30
|
+
const IMPACT_MAP = {
|
|
31
|
+
critical: { severity: '1-Critical', priority: 'Blocker' },
|
|
32
|
+
serious: { severity: '2-Severe', priority: 'High' },
|
|
33
|
+
moderate: { severity: '3-Average', priority: 'Medium' },
|
|
34
|
+
minor: { severity: '4-Low', priority: 'Low' },
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// AxeCore WCAG tag strings → human-readable criteria
|
|
38
|
+
const WCAG_TAGS = {
|
|
39
|
+
wcag111: '1.1.1 (Level A)',
|
|
40
|
+
wcag121: '1.2.1 (Level A)',
|
|
41
|
+
wcag122: '1.2.2 (Level A)',
|
|
42
|
+
wcag123: '1.2.3 (Level A)',
|
|
43
|
+
wcag124: '1.2.4 (Level AA)',
|
|
44
|
+
wcag125: '1.2.5 (Level AA)',
|
|
45
|
+
wcag131: '1.3.1 (Level A)',
|
|
46
|
+
wcag132: '1.3.2 (Level A)',
|
|
47
|
+
wcag133: '1.3.3 (Level A)',
|
|
48
|
+
wcag134: '1.3.4 (Level AA)',
|
|
49
|
+
wcag135: '1.3.5 (Level AA)',
|
|
50
|
+
wcag141: '1.4.1 (Level A)',
|
|
51
|
+
wcag142: '1.4.2 (Level A)',
|
|
52
|
+
wcag143: '1.4.3 (Level AA)',
|
|
53
|
+
wcag144: '1.4.4 (Level AA)',
|
|
54
|
+
wcag145: '1.4.5 (Level AA)',
|
|
55
|
+
wcag1410: '1.4.10 (Level AA)',
|
|
56
|
+
wcag1411: '1.4.11 (Level AA)',
|
|
57
|
+
wcag1412: '1.4.12 (Level AA)',
|
|
58
|
+
wcag1413: '1.4.13 (Level AA)',
|
|
59
|
+
wcag211: '2.1.1 (Level A)',
|
|
60
|
+
wcag212: '2.1.2 (Level A)',
|
|
61
|
+
wcag214: '2.1.4 (Level A)',
|
|
62
|
+
wcag221: '2.2.1 (Level A)',
|
|
63
|
+
wcag222: '2.2.2 (Level A)',
|
|
64
|
+
wcag231: '2.3.1 (Level A)',
|
|
65
|
+
wcag241: '2.4.1 (Level A)',
|
|
66
|
+
wcag242: '2.4.2 (Level A)',
|
|
67
|
+
wcag243: '2.4.3 (Level A)',
|
|
68
|
+
wcag244: '2.4.4 (Level A)',
|
|
69
|
+
wcag245: '2.4.5 (Level AA)',
|
|
70
|
+
wcag246: '2.4.6 (Level AA)',
|
|
71
|
+
wcag247: '2.4.7 (Level AA)',
|
|
72
|
+
wcag248: '2.4.8 (Level AAA)',
|
|
73
|
+
wcag249: '2.4.9 (Level AAA)',
|
|
74
|
+
wcag251: '2.5.1 (Level A)',
|
|
75
|
+
wcag252: '2.5.2 (Level A)',
|
|
76
|
+
wcag253: '2.5.3 (Level A)',
|
|
77
|
+
wcag254: '2.5.4 (Level A)',
|
|
78
|
+
wcag311: '3.1.1 (Level A)',
|
|
79
|
+
wcag312: '3.1.2 (Level AA)',
|
|
80
|
+
wcag321: '3.2.1 (Level A)',
|
|
81
|
+
wcag322: '3.2.2 (Level A)',
|
|
82
|
+
wcag323: '3.2.3 (Level AA)',
|
|
83
|
+
wcag324: '3.2.4 (Level AA)',
|
|
84
|
+
wcag331: '3.3.1 (Level A)',
|
|
85
|
+
wcag332: '3.3.2 (Level A)',
|
|
86
|
+
wcag333: '3.3.3 (Level AA)',
|
|
87
|
+
wcag334: '3.3.4 (Level AA)',
|
|
88
|
+
wcag411: '4.1.1 (Level A)',
|
|
89
|
+
wcag412: '4.1.2 (Level A)',
|
|
90
|
+
wcag413: '4.1.3 (Level AA)',
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
function parseWcagTags(tags) {
|
|
94
|
+
return tags
|
|
95
|
+
.filter(t => t.startsWith('wcag') && /\d/.test(t))
|
|
96
|
+
.map(t => WCAG_TAGS[t])
|
|
97
|
+
.filter(Boolean);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function extractElementType(html) {
|
|
101
|
+
const match = html && html.match(/^<([a-z][a-z0-9-]*)/i);
|
|
102
|
+
return match ? match[1].toLowerCase() : 'unknown';
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function formatViolationAsMarkdown(violation, node, context) {
|
|
106
|
+
const impact = (violation.impact || 'moderate').toLowerCase();
|
|
107
|
+
const { severity, priority } = IMPACT_MAP[impact] || IMPACT_MAP.moderate;
|
|
108
|
+
const wcagCriteria = parseWcagTags(violation.tags || []);
|
|
109
|
+
const elementType = extractElementType(node.html);
|
|
110
|
+
const selector = Array.isArray(node.target) ? node.target.join(', ') : (node.target || '');
|
|
111
|
+
|
|
112
|
+
const lines = [
|
|
113
|
+
`## ${violation.help || violation.description}`,
|
|
114
|
+
'',
|
|
115
|
+
`**Severity:** ${severity} `,
|
|
116
|
+
`**Priority:** ${priority}`,
|
|
117
|
+
'',
|
|
118
|
+
'### Problem Statement',
|
|
119
|
+
violation.description || '',
|
|
120
|
+
'',
|
|
121
|
+
'### Element',
|
|
122
|
+
`- **Type:** \`${elementType}\``,
|
|
123
|
+
`- **Selector:** \`${selector}\``,
|
|
124
|
+
'',
|
|
125
|
+
'### Code Reference',
|
|
126
|
+
'```html',
|
|
127
|
+
node.html || '',
|
|
128
|
+
'```',
|
|
129
|
+
'',
|
|
130
|
+
'### Failure Summary',
|
|
131
|
+
node.failureSummary || '',
|
|
132
|
+
'',
|
|
133
|
+
];
|
|
134
|
+
|
|
135
|
+
if (wcagCriteria.length > 0) {
|
|
136
|
+
lines.push('### WCAG Criteria');
|
|
137
|
+
wcagCriteria.forEach(c => lines.push(`- ${c}`));
|
|
138
|
+
lines.push('');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (violation.helpUrl) {
|
|
142
|
+
lines.push(`### Resources`);
|
|
143
|
+
lines.push(`- [Axe Rule: ${violation.id}](${violation.helpUrl})`);
|
|
144
|
+
lines.push('');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (context) {
|
|
148
|
+
lines.push('### Environment');
|
|
149
|
+
if (context.url) lines.push(`- **URL:** ${context.url}`);
|
|
150
|
+
if (context.browser) lines.push(`- **Browser:** ${context.browser}`);
|
|
151
|
+
if (context.operatingSystem) lines.push(`- **OS:** ${context.operatingSystem}`);
|
|
152
|
+
if (context.buildVersion) lines.push(`- **Build:** ${context.buildVersion}`);
|
|
153
|
+
if (context.stepsToReproduce) {
|
|
154
|
+
lines.push('');
|
|
155
|
+
lines.push('**Steps to Reproduce:**');
|
|
156
|
+
lines.push(context.stepsToReproduce);
|
|
157
|
+
}
|
|
158
|
+
lines.push('');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return lines.join('\n');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function formatViolationAsJSON(violation, node, context) {
|
|
165
|
+
const impact = (violation.impact || 'moderate').toLowerCase();
|
|
166
|
+
const { severity, priority } = IMPACT_MAP[impact] || IMPACT_MAP.moderate;
|
|
167
|
+
const wcagCriteria = parseWcagTags(violation.tags || []);
|
|
168
|
+
const elementType = extractElementType(node.html);
|
|
169
|
+
const selector = Array.isArray(node.target) ? node.target.join(', ') : (node.target || '');
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
ruleId: violation.id,
|
|
173
|
+
severity,
|
|
174
|
+
priority,
|
|
175
|
+
wcagCriteria,
|
|
176
|
+
problemStatement: violation.description || '',
|
|
177
|
+
impact: violation.impact,
|
|
178
|
+
element: {
|
|
179
|
+
type: elementType,
|
|
180
|
+
selector,
|
|
181
|
+
html: node.html || '',
|
|
182
|
+
},
|
|
183
|
+
failureSummary: node.failureSummary || '',
|
|
184
|
+
helpUrl: violation.helpUrl || '',
|
|
185
|
+
...(context ? { context } : {}),
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export const tools = [
|
|
190
|
+
{
|
|
191
|
+
name: 'list_issue_templates',
|
|
192
|
+
description: 'Lists all available accessibility issue template files (web and native).',
|
|
193
|
+
inputSchema: {
|
|
194
|
+
type: 'object',
|
|
195
|
+
properties: {},
|
|
196
|
+
required: [],
|
|
197
|
+
},
|
|
198
|
+
handler: async () => {
|
|
199
|
+
const index = await loadIndex();
|
|
200
|
+
const lines = index.templates.map(
|
|
201
|
+
t => `- **${t.name}** (${t.platform}): ${t.description}`
|
|
202
|
+
);
|
|
203
|
+
return textResponse(`Available templates:\n\n${lines.join('\n')}`);
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
|
|
207
|
+
{
|
|
208
|
+
name: 'get_issue_template',
|
|
209
|
+
description: 'Retrieves the full content of an accessibility issue template by name.',
|
|
210
|
+
inputSchema: {
|
|
211
|
+
type: 'object',
|
|
212
|
+
properties: {
|
|
213
|
+
templateName: {
|
|
214
|
+
type: 'string',
|
|
215
|
+
description: 'Template filename, e.g. "web-issue-templates.md" or "native-issue-templates.md"',
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
required: ['templateName'],
|
|
219
|
+
},
|
|
220
|
+
handler: async (args) => {
|
|
221
|
+
const index = await loadIndex();
|
|
222
|
+
const entry = index.templates.find(t => t.name === args.templateName);
|
|
223
|
+
if (!entry) {
|
|
224
|
+
const names = index.templates.map(t => t.name).join(', ');
|
|
225
|
+
return textResponse(`Template "${args.templateName}" not found. Available: ${names}`);
|
|
226
|
+
}
|
|
227
|
+
const content = await readTemplate(entry, index.packageName);
|
|
228
|
+
return textResponse(content);
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
|
|
232
|
+
{
|
|
233
|
+
name: 'format_violations',
|
|
234
|
+
description:
|
|
235
|
+
'Converts one or more AxeCore accessibility violations into formatted issue templates.',
|
|
236
|
+
inputSchema: {
|
|
237
|
+
type: 'object',
|
|
238
|
+
properties: {
|
|
239
|
+
violations: {
|
|
240
|
+
description:
|
|
241
|
+
'A single AxeCore violation object or an array of them.',
|
|
242
|
+
oneOf: [
|
|
243
|
+
{ type: 'object' },
|
|
244
|
+
{ type: 'array', items: { type: 'object' } },
|
|
245
|
+
],
|
|
246
|
+
},
|
|
247
|
+
context: {
|
|
248
|
+
type: 'object',
|
|
249
|
+
description: 'Optional context about the page/environment.',
|
|
250
|
+
properties: {
|
|
251
|
+
url: { type: 'string' },
|
|
252
|
+
browser: { type: 'string' },
|
|
253
|
+
operatingSystem: { type: 'string' },
|
|
254
|
+
buildVersion: { type: 'string' },
|
|
255
|
+
stepsToReproduce: { type: 'string' },
|
|
256
|
+
},
|
|
257
|
+
},
|
|
258
|
+
outputFormat: {
|
|
259
|
+
type: 'string',
|
|
260
|
+
enum: ['markdown', 'json'],
|
|
261
|
+
description: 'Output format. Defaults to "markdown".',
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
required: ['violations'],
|
|
265
|
+
},
|
|
266
|
+
handler: async (args) => {
|
|
267
|
+
const violations = Array.isArray(args.violations)
|
|
268
|
+
? args.violations
|
|
269
|
+
: [args.violations];
|
|
270
|
+
const context = args.context || null;
|
|
271
|
+
const format = args.outputFormat === 'json' ? 'json' : 'markdown';
|
|
272
|
+
|
|
273
|
+
const results = [];
|
|
274
|
+
for (const violation of violations) {
|
|
275
|
+
const nodes = violation.nodes || [{}];
|
|
276
|
+
for (const node of nodes) {
|
|
277
|
+
if (format === 'json') {
|
|
278
|
+
results.push(formatViolationAsJSON(violation, node, context));
|
|
279
|
+
} else {
|
|
280
|
+
results.push(formatViolationAsMarkdown(violation, node, context));
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (format === 'json') {
|
|
286
|
+
return textResponse(JSON.stringify(results, null, 2));
|
|
287
|
+
}
|
|
288
|
+
return textResponse(results.join('\n---\n\n'));
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
|
|
292
|
+
{
|
|
293
|
+
name: 'validate_issue',
|
|
294
|
+
description: 'Validates that a formatted accessibility issue contains all required sections.',
|
|
295
|
+
inputSchema: {
|
|
296
|
+
type: 'object',
|
|
297
|
+
properties: {
|
|
298
|
+
issueContent: {
|
|
299
|
+
type: 'string',
|
|
300
|
+
description: 'The formatted issue content (markdown string) to validate.',
|
|
301
|
+
},
|
|
302
|
+
},
|
|
303
|
+
required: ['issueContent'],
|
|
304
|
+
},
|
|
305
|
+
handler: async (args) => {
|
|
306
|
+
const content = args.issueContent || '';
|
|
307
|
+
|
|
308
|
+
const checks = [
|
|
309
|
+
{
|
|
310
|
+
label: 'Severity',
|
|
311
|
+
pass: /\*\*Severity\*\*\s*:/i.test(content) || /severity/i.test(content),
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
label: 'Priority',
|
|
315
|
+
pass: /\*\*Priority\*\*\s*:/i.test(content) || /priority/i.test(content),
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
label: 'Element or selector reference',
|
|
319
|
+
pass: /selector|element|`[^`]+`/i.test(content),
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
label: 'Code block',
|
|
323
|
+
pass: /```/.test(content),
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
label: 'Failure or problem description',
|
|
327
|
+
pass: /failure|problem|issue|description/i.test(content),
|
|
328
|
+
},
|
|
329
|
+
];
|
|
330
|
+
|
|
331
|
+
const failures = checks.filter(c => !c.pass).map(c => c.label);
|
|
332
|
+
const passed = failures.length === 0;
|
|
333
|
+
|
|
334
|
+
if (passed) {
|
|
335
|
+
return textResponse('Validation passed: issue contains all required sections.');
|
|
336
|
+
}
|
|
337
|
+
return textResponse(
|
|
338
|
+
`Validation failed. Missing sections:\n${failures.map(f => `- ${f}`).join('\n')}`
|
|
339
|
+
);
|
|
340
|
+
},
|
|
341
|
+
},
|
|
342
|
+
];
|