@weaaare/mcp-nvda-auditor 0.1.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/LICENSE +21 -0
- package/README.md +360 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/prompts/audit-prompts.d.ts +4 -0
- package/dist/prompts/audit-prompts.d.ts.map +1 -0
- package/dist/prompts/audit-prompts.js +428 -0
- package/dist/prompts/audit-prompts.js.map +1 -0
- package/dist/screen-readers/nvda/nvda.adapter.d.ts +25 -0
- package/dist/screen-readers/nvda/nvda.adapter.d.ts.map +1 -0
- package/dist/screen-readers/nvda/nvda.adapter.js +208 -0
- package/dist/screen-readers/nvda/nvda.adapter.js.map +1 -0
- package/dist/screen-readers/nvda/nvda.tools.d.ts +3 -0
- package/dist/screen-readers/nvda/nvda.tools.d.ts.map +1 -0
- package/dist/screen-readers/nvda/nvda.tools.js +442 -0
- package/dist/screen-readers/nvda/nvda.tools.js.map +1 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +57 -0
- package/dist/server.js.map +1 -0
- package/dist/setup/setup-check.tools.d.ts +3 -0
- package/dist/setup/setup-check.tools.d.ts.map +1 -0
- package/dist/setup/setup-check.tools.js +52 -0
- package/dist/setup/setup-check.tools.js.map +1 -0
- package/package.json +57 -0
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
import { getPromptMessages, listPrompts } from '@weaaare/mcp-auditor-core';
|
|
2
|
+
// ─── Shared lifecycle note ────────────────────────────────────────────────────
|
|
3
|
+
// Every NVDA audit follows this discipline:
|
|
4
|
+
// 1. check_setup → nvda_start → navigate to URL → start_audit
|
|
5
|
+
// 2. … audit work using nvda_perform, nvda_press, nvda_next, nvda_last_spoken_phrase …
|
|
6
|
+
// 3. end_audit → generate_report → nvda_stop
|
|
7
|
+
const fullAccessibilityAudit = {
|
|
8
|
+
prompt: {
|
|
9
|
+
name: 'full_accessibility_audit',
|
|
10
|
+
description: 'Comprehensive NVDA screen reader accessibility audit of a web page. Covers headings, landmarks, links, forms, images, tables, and keyboard navigation.',
|
|
11
|
+
arguments: [
|
|
12
|
+
{
|
|
13
|
+
name: 'url',
|
|
14
|
+
description: 'URL of the page to audit',
|
|
15
|
+
required: true,
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
name: 'browser',
|
|
19
|
+
description: 'Browser to use (default: Chrome)',
|
|
20
|
+
required: false,
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: 'wcagLevel',
|
|
24
|
+
description: 'WCAG conformance level: A, AA, or AAA (default: AA)',
|
|
25
|
+
required: false,
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
},
|
|
29
|
+
getMessage: (args) => {
|
|
30
|
+
const url = args.url ?? 'UNKNOWN_URL';
|
|
31
|
+
const browser = args.browser ?? 'Chrome';
|
|
32
|
+
const wcagLevel = args.wcagLevel ?? 'AA';
|
|
33
|
+
return [
|
|
34
|
+
{
|
|
35
|
+
role: 'user',
|
|
36
|
+
content: {
|
|
37
|
+
type: 'text',
|
|
38
|
+
text: `Perform a comprehensive NVDA accessibility audit of ${url}. Follow every step in order.
|
|
39
|
+
|
|
40
|
+
## Phase 1 — Environment & NVDA Startup
|
|
41
|
+
1. \`check_setup\` — verify Windows environment (NVDA is Windows-only)
|
|
42
|
+
2. \`nvda_start\` — start NVDA
|
|
43
|
+
3. Navigate to ${url} in ${browser} (use Playwright MCP, browser dev tools, or instruct the user)
|
|
44
|
+
4. \`start_audit\` with url "${url}", screenReader "nvda", wcagLevel "${wcagLevel}"
|
|
45
|
+
|
|
46
|
+
**Browse mode note:** NVDA uses browse mode by default for web content. Single-letter commands (H, K, F, D…) navigate by element type. To interact with a specific control (e.g. type in a field), toggle to focus mode with \`nvda_perform\` → toggleBetweenBrowseAndFocusMode, interact, then press Escape to return to browse mode.
|
|
47
|
+
|
|
48
|
+
## Phase 2 — Audit Workflow
|
|
49
|
+
|
|
50
|
+
### 2.1 Page Title & Language
|
|
51
|
+
- \`nvda_perform\` → reportTitle — check what NVDA announces as the page title
|
|
52
|
+
- Verify the title is descriptive and unique (WCAG 2.4.2 Page Titled)
|
|
53
|
+
- \`nvda_last_spoken_phrase\` — capture the announcement
|
|
54
|
+
- \`log_finding\` — log pass or violation
|
|
55
|
+
|
|
56
|
+
### 2.2 Heading Structure
|
|
57
|
+
- \`nvda_perform\` → moveToNextHeading (repeat until NVDA announces "no next heading" or similar)
|
|
58
|
+
- For each heading: \`nvda_last_spoken_phrase\` and \`nvda_item_text\` — record level and text
|
|
59
|
+
- Also audit specific levels: \`nvda_perform\` → moveToNextHeadingLevel1 (expect exactly one h1)
|
|
60
|
+
- Use \`nvda_perform\` → browseModeElementsList to open the Elements List dialog (NVDA+F7) — it shows all headings in a navigable list for a complete overview
|
|
61
|
+
- Verify:
|
|
62
|
+
- Exactly one h1 on the page (WCAG 2.4.6)
|
|
63
|
+
- No skipped levels (e.g., h1 → h3 without h2 is a violation)
|
|
64
|
+
- Heading text is descriptive
|
|
65
|
+
- \`log_finding\` for each issue (WCAG 1.3.1, 2.4.6)
|
|
66
|
+
|
|
67
|
+
### 2.3 Landmark Regions
|
|
68
|
+
- \`nvda_perform\` → moveToNextLandmark (repeat until exhausted)
|
|
69
|
+
- For each landmark: \`nvda_last_spoken_phrase\` — identify its type (banner, navigation, main, contentinfo, complementary)
|
|
70
|
+
- Required: \`main\` landmark must exist. Recommended: \`navigation\` and \`contentinfo\`.
|
|
71
|
+
- \`log_finding\` for missing required landmarks (WCAG 1.3.1, 2.4.1)
|
|
72
|
+
|
|
73
|
+
### 2.4 Links
|
|
74
|
+
- \`nvda_perform\` → moveToNextLink (repeat until exhausted)
|
|
75
|
+
- For each link: \`nvda_last_spoken_phrase\` — check announced text
|
|
76
|
+
- Watch for vague link text: "click here", "read more", "here", "more", URLs as link text
|
|
77
|
+
- Also audit visited vs unvisited: \`nvda_perform\` → moveToNextUnvisitedLink
|
|
78
|
+
- \`log_finding\` for vague links (WCAG 2.4.4 Link Purpose in Context)
|
|
79
|
+
|
|
80
|
+
### 2.5 Forms
|
|
81
|
+
Navigate by specific control type for thorough coverage:
|
|
82
|
+
- **Text inputs / selects:** \`nvda_perform\` → moveToNextEditField / moveToNextFormField (repeat)
|
|
83
|
+
- **Buttons:** \`nvda_perform\` → moveToNextButton (repeat)
|
|
84
|
+
- **Checkboxes:** \`nvda_perform\` → moveToNextCheckbox (repeat)
|
|
85
|
+
- **Radio buttons:** \`nvda_perform\` → moveToNextRadioButton (repeat)
|
|
86
|
+
- **Combo boxes:** \`nvda_perform\` → moveToNextComboBox (repeat)
|
|
87
|
+
|
|
88
|
+
For each control:
|
|
89
|
+
a. \`nvda_last_spoken_phrase\` — check label, role, required state, current value
|
|
90
|
+
b. Verify label is announced (tab to control with \`nvda_press\` 'Tab')
|
|
91
|
+
c. Toggle to focus mode (\`nvda_perform\` → toggleBetweenBrowseAndFocusMode) to interact
|
|
92
|
+
d. For text fields: type invalid data, check error messages are announced
|
|
93
|
+
e. Return to browse mode (Escape key via \`nvda_press\` 'Escape')
|
|
94
|
+
- \`log_finding\` per issue (WCAG 1.3.1, 3.3.1, 3.3.2, 4.1.2)
|
|
95
|
+
|
|
96
|
+
### 2.6 Images and Graphics
|
|
97
|
+
- \`nvda_perform\` → moveToNextGraphic (repeat until exhausted)
|
|
98
|
+
- For each image: \`nvda_last_spoken_phrase\` — check alt text announcement
|
|
99
|
+
- Informative images: must have descriptive alt text
|
|
100
|
+
- Decorative images: must be hidden (NVDA should skip them or announce "graphic" with empty alt)
|
|
101
|
+
- Complex images (charts/diagrams): check for long description (\`nvda_perform\` → openLongDescription)
|
|
102
|
+
- \`log_finding\` per issue (WCAG 1.1.1 Non-text Content)
|
|
103
|
+
|
|
104
|
+
### 2.7 Tables
|
|
105
|
+
- \`nvda_perform\` → moveToNextTable (repeat)
|
|
106
|
+
- For each table: navigate cells with moveToNextColumn/moveToNextRow
|
|
107
|
+
- \`nvda_last_spoken_phrase\` — NVDA should announce column/row header context with each cell
|
|
108
|
+
- Verify headers are associated with data cells
|
|
109
|
+
- Check table caption or summary if present
|
|
110
|
+
- \`log_finding\` per issue (WCAG 1.3.1)
|
|
111
|
+
|
|
112
|
+
### 2.8 Keyboard Navigation
|
|
113
|
+
- Press Tab from the start of the page (\`nvda_press\` 'Tab' repeatedly)
|
|
114
|
+
- Verify logical focus order — matches reading/visual order
|
|
115
|
+
- Verify no keyboard traps — you can always Tab out of any component
|
|
116
|
+
- Verify all interactive elements are reachable with keyboard
|
|
117
|
+
- Pay particular attention to modals, menus, and custom widgets
|
|
118
|
+
- \`log_finding\` per issue (WCAG 2.1.1, 2.1.2, 2.4.3)
|
|
119
|
+
|
|
120
|
+
### 2.9 Dynamic Content & Live Regions
|
|
121
|
+
- Trigger dynamic updates (form submissions, AJAX, notifications)
|
|
122
|
+
- \`nvda_last_spoken_phrase\` — verify NVDA announces changes automatically
|
|
123
|
+
- Check that status messages are announced without moving focus (WCAG 4.1.3)
|
|
124
|
+
- \`log_finding\` per issue
|
|
125
|
+
|
|
126
|
+
## Handling Accessibility Failures
|
|
127
|
+
- If NVDA cannot navigate to an element at all → that IS the finding — log it as a violation
|
|
128
|
+
- If toggling browse/focus mode is needed → use \`nvda_perform\` → toggleBetweenBrowseAndFocusMode
|
|
129
|
+
- If the Elements List dialog is useful → \`nvda_perform\` → browseModeElementsList
|
|
130
|
+
|
|
131
|
+
## Phase 3 — Report & Shutdown
|
|
132
|
+
1. \`end_audit\`
|
|
133
|
+
2. \`generate_report\` with format "markdown"
|
|
134
|
+
3. Present findings — critical violations first, then warnings, then passes
|
|
135
|
+
4. \`nvda_stop\` — stop NVDA`,
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
];
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
const headingStructureAudit = {
|
|
142
|
+
prompt: {
|
|
143
|
+
name: 'heading_structure_audit',
|
|
144
|
+
description: 'Audit heading structure using NVDA. Checks h1 uniqueness, hierarchy, and descriptive content.',
|
|
145
|
+
arguments: [
|
|
146
|
+
{
|
|
147
|
+
name: 'url',
|
|
148
|
+
description: 'URL of the page to audit',
|
|
149
|
+
required: true,
|
|
150
|
+
},
|
|
151
|
+
],
|
|
152
|
+
},
|
|
153
|
+
getMessage: (args) => {
|
|
154
|
+
const url = args.url ?? 'UNKNOWN_URL';
|
|
155
|
+
return [
|
|
156
|
+
{
|
|
157
|
+
role: 'user',
|
|
158
|
+
content: {
|
|
159
|
+
type: 'text',
|
|
160
|
+
text: `Audit the heading structure of ${url} using NVDA.
|
|
161
|
+
|
|
162
|
+
## Setup
|
|
163
|
+
1. \`check_setup\` — verify Windows environment
|
|
164
|
+
2. \`nvda_start\` — start NVDA
|
|
165
|
+
3. Navigate to ${url} in browser
|
|
166
|
+
4. \`start_audit\` with url "${url}"
|
|
167
|
+
|
|
168
|
+
## Overview with Elements List
|
|
169
|
+
5. \`nvda_perform\` → browseModeElementsList
|
|
170
|
+
Opens the NVDA Elements List dialog (NVDA+F7). Select "Headings" from the dropdown.
|
|
171
|
+
This shows all headings with their levels — ideal for a complete structural overview.
|
|
172
|
+
Press Escape to close the dialog after reviewing.
|
|
173
|
+
|
|
174
|
+
## Heading Traversal
|
|
175
|
+
6. \`nvda_perform\` → moveToNextHeading — repeat until exhausted
|
|
176
|
+
For each heading: \`nvda_last_spoken_phrase\` and \`nvda_item_text\`
|
|
177
|
+
Record: heading level (h1–h6) and text content.
|
|
178
|
+
|
|
179
|
+
7. Verify level 1 specifically:
|
|
180
|
+
\`nvda_perform\` → moveToNextHeadingLevel1 (should find exactly ONE h1)
|
|
181
|
+
\`nvda_perform\` → moveToNextHeadingLevel1 again (should find no more)
|
|
182
|
+
|
|
183
|
+
## Verification
|
|
184
|
+
8. Check:
|
|
185
|
+
- Exactly one h1 on the page (WCAG 2.4.6)
|
|
186
|
+
- No skipped heading levels (h1 → h3 without h2 is a violation)
|
|
187
|
+
- All headings are descriptive (not empty, not generic like "Section")
|
|
188
|
+
- Heading hierarchy reflects the logical document structure
|
|
189
|
+
|
|
190
|
+
9. \`log_finding\` for each issue found
|
|
191
|
+
|
|
192
|
+
## Wrap-up
|
|
193
|
+
10. \`end_audit\`
|
|
194
|
+
11. \`nvda_stop\` — stop NVDA
|
|
195
|
+
12. \`generate_report\` with format "markdown"`,
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
];
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
const formAccessibilityAudit = {
|
|
202
|
+
prompt: {
|
|
203
|
+
name: 'form_accessibility_audit',
|
|
204
|
+
description: 'Audit form accessibility with NVDA: labels, error messages, required fields, and keyboard navigation.',
|
|
205
|
+
arguments: [
|
|
206
|
+
{
|
|
207
|
+
name: 'url',
|
|
208
|
+
description: 'URL of the page with forms to audit',
|
|
209
|
+
required: true,
|
|
210
|
+
},
|
|
211
|
+
],
|
|
212
|
+
},
|
|
213
|
+
getMessage: (args) => {
|
|
214
|
+
const url = args.url ?? 'UNKNOWN_URL';
|
|
215
|
+
return [
|
|
216
|
+
{
|
|
217
|
+
role: 'user',
|
|
218
|
+
content: {
|
|
219
|
+
type: 'text',
|
|
220
|
+
text: `Audit form accessibility on ${url} using NVDA.
|
|
221
|
+
|
|
222
|
+
## Setup
|
|
223
|
+
1. \`check_setup\` — verify Windows environment
|
|
224
|
+
2. \`nvda_start\` — start NVDA
|
|
225
|
+
3. Navigate to ${url} in browser
|
|
226
|
+
4. \`start_audit\` with url "${url}"
|
|
227
|
+
|
|
228
|
+
## Form Controls
|
|
229
|
+
Navigate by control type for thorough coverage:
|
|
230
|
+
|
|
231
|
+
a. **Edit fields & text inputs:**
|
|
232
|
+
\`nvda_perform\` → moveToNextEditField (repeat)
|
|
233
|
+
\`nvda_last_spoken_phrase\` — check label text, role, required state
|
|
234
|
+
|
|
235
|
+
b. **General form fields (includes selects, textareas):**
|
|
236
|
+
\`nvda_perform\` → moveToNextFormField (repeat)
|
|
237
|
+
\`nvda_last_spoken_phrase\` — check complete announcement
|
|
238
|
+
|
|
239
|
+
c. **Buttons:**
|
|
240
|
+
\`nvda_perform\` → moveToNextButton (repeat)
|
|
241
|
+
\`nvda_last_spoken_phrase\` — check button label
|
|
242
|
+
|
|
243
|
+
d. **Checkboxes:**
|
|
244
|
+
\`nvda_perform\` → moveToNextCheckbox (repeat)
|
|
245
|
+
\`nvda_last_spoken_phrase\` — check label and checked/unchecked state
|
|
246
|
+
|
|
247
|
+
e. **Radio buttons:**
|
|
248
|
+
\`nvda_perform\` → moveToNextRadioButton (repeat)
|
|
249
|
+
\`nvda_last_spoken_phrase\` — check label, group name, selected state
|
|
250
|
+
|
|
251
|
+
f. **Combo boxes / dropdowns:**
|
|
252
|
+
\`nvda_perform\` → moveToNextComboBox (repeat)
|
|
253
|
+
\`nvda_last_spoken_phrase\` — check label and current value
|
|
254
|
+
|
|
255
|
+
## Interaction Testing (for each control that accepts input)
|
|
256
|
+
5. Tab to the control (\`nvda_press\` 'Tab')
|
|
257
|
+
6. \`nvda_perform\` → toggleBetweenBrowseAndFocusMode — enter focus mode
|
|
258
|
+
7. Interact: type invalid data, select options, check/uncheck
|
|
259
|
+
8. Submit or trigger validation
|
|
260
|
+
9. \`nvda_last_spoken_phrase\` — check if error messages are announced
|
|
261
|
+
10. Press Escape (\`nvda_press\` 'Escape') to return to browse mode
|
|
262
|
+
|
|
263
|
+
## Verification Checklist
|
|
264
|
+
- All inputs have associated labels (WCAG 1.3.1, 3.3.2)
|
|
265
|
+
- Required fields are announced as required (WCAG 3.3.2)
|
|
266
|
+
- Error messages are announced — either live region or associated with field (WCAG 3.3.1)
|
|
267
|
+
- Form is completable entirely by keyboard (WCAG 2.1.1)
|
|
268
|
+
- Submit button is reachable by Tab (WCAG 2.1.1)
|
|
269
|
+
|
|
270
|
+
11. \`log_finding\` for each issue
|
|
271
|
+
|
|
272
|
+
## Wrap-up
|
|
273
|
+
12. \`end_audit\`
|
|
274
|
+
13. \`nvda_stop\` — stop NVDA
|
|
275
|
+
14. \`generate_report\` with format "markdown"`,
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
];
|
|
279
|
+
},
|
|
280
|
+
};
|
|
281
|
+
const navigationAudit = {
|
|
282
|
+
prompt: {
|
|
283
|
+
name: 'navigation_audit',
|
|
284
|
+
description: 'Audit keyboard navigation and landmark regions with NVDA. Checks tab order, landmarks, skip links, and focus management.',
|
|
285
|
+
arguments: [
|
|
286
|
+
{
|
|
287
|
+
name: 'url',
|
|
288
|
+
description: 'URL of the page to audit',
|
|
289
|
+
required: true,
|
|
290
|
+
},
|
|
291
|
+
],
|
|
292
|
+
},
|
|
293
|
+
getMessage: (args) => {
|
|
294
|
+
const url = args.url ?? 'UNKNOWN_URL';
|
|
295
|
+
return [
|
|
296
|
+
{
|
|
297
|
+
role: 'user',
|
|
298
|
+
content: {
|
|
299
|
+
type: 'text',
|
|
300
|
+
text: `Audit keyboard navigation and landmark regions on ${url} using NVDA.
|
|
301
|
+
|
|
302
|
+
## Setup
|
|
303
|
+
1. \`check_setup\` — verify Windows environment
|
|
304
|
+
2. \`nvda_start\` — start NVDA
|
|
305
|
+
3. Navigate to ${url} in browser
|
|
306
|
+
4. \`start_audit\` with url "${url}"
|
|
307
|
+
|
|
308
|
+
## Skip Links
|
|
309
|
+
5. Press Tab once from the top of the page (\`nvda_press\` 'Tab')
|
|
310
|
+
- Check if a "Skip to main content" or "Skip navigation" link appears
|
|
311
|
+
- \`nvda_last_spoken_phrase\` — verify the link text is descriptive
|
|
312
|
+
- Activate it (\`nvda_act\`) and verify focus moves to the main content area
|
|
313
|
+
|
|
314
|
+
## Landmark Regions
|
|
315
|
+
6. \`nvda_perform\` → moveToNextLandmark (repeat until exhausted)
|
|
316
|
+
For each landmark: \`nvda_last_spoken_phrase\` — note the type announced
|
|
317
|
+
Expected landmark types: banner, navigation, main, contentinfo, complementary, search, form, region
|
|
318
|
+
- \`main\` landmark is required (WCAG 1.3.1)
|
|
319
|
+
- \`navigation\` (nav) is strongly recommended
|
|
320
|
+
- \`contentinfo\` (footer) is recommended
|
|
321
|
+
7. \`log_finding\` for missing required or expected landmarks
|
|
322
|
+
|
|
323
|
+
## Tab Order
|
|
324
|
+
8. From page start, press Tab repeatedly (\`nvda_press\` 'Tab')
|
|
325
|
+
- \`nvda_last_spoken_phrase\` after each Tab to track focus position
|
|
326
|
+
- Verify order follows logical/visual reading order (WCAG 2.4.3)
|
|
327
|
+
- Verify all interactive elements are reachable (links, buttons, inputs, selects)
|
|
328
|
+
- Verify there are no keyboard traps — you can always Tab out (WCAG 2.1.2)
|
|
329
|
+
- Watch for invisible or off-screen focused elements
|
|
330
|
+
|
|
331
|
+
## Focus Management
|
|
332
|
+
9. If the page has modals/dialogs:
|
|
333
|
+
- Trigger the modal (click/Enter)
|
|
334
|
+
- Verify focus moves into the modal automatically
|
|
335
|
+
- Verify focus is trapped within modal while open (Tab should cycle within modal)
|
|
336
|
+
- Close modal and verify focus returns to trigger element
|
|
337
|
+
10. If the page has expandable sections (accordions, disclosures):
|
|
338
|
+
- Toggle open — verify appropriate focus or announcement
|
|
339
|
+
|
|
340
|
+
## Findings
|
|
341
|
+
11. \`log_finding\` per issue with applicable WCAG criteria:
|
|
342
|
+
- 2.4.1 Bypass Blocks (skip link)
|
|
343
|
+
- 2.4.3 Focus Order
|
|
344
|
+
- 2.1.1 Keyboard
|
|
345
|
+
- 2.1.2 No Keyboard Trap
|
|
346
|
+
- 1.3.1 Info and Relationships (landmarks)
|
|
347
|
+
|
|
348
|
+
## Wrap-up
|
|
349
|
+
12. \`end_audit\`
|
|
350
|
+
13. \`nvda_stop\` — stop NVDA
|
|
351
|
+
14. \`generate_report\` with format "markdown"`,
|
|
352
|
+
},
|
|
353
|
+
},
|
|
354
|
+
];
|
|
355
|
+
},
|
|
356
|
+
};
|
|
357
|
+
const imageAudit = {
|
|
358
|
+
prompt: {
|
|
359
|
+
name: 'image_accessibility_audit',
|
|
360
|
+
description: 'Audit image accessibility with NVDA. Checks alt text, decorative image handling, and complex image descriptions.',
|
|
361
|
+
arguments: [
|
|
362
|
+
{
|
|
363
|
+
name: 'url',
|
|
364
|
+
description: 'URL of the page to audit',
|
|
365
|
+
required: true,
|
|
366
|
+
},
|
|
367
|
+
],
|
|
368
|
+
},
|
|
369
|
+
getMessage: (args) => {
|
|
370
|
+
const url = args.url ?? 'UNKNOWN_URL';
|
|
371
|
+
return [
|
|
372
|
+
{
|
|
373
|
+
role: 'user',
|
|
374
|
+
content: {
|
|
375
|
+
type: 'text',
|
|
376
|
+
text: `Audit image accessibility on ${url} using NVDA.
|
|
377
|
+
|
|
378
|
+
## Setup
|
|
379
|
+
1. \`check_setup\` — verify Windows environment
|
|
380
|
+
2. \`nvda_start\` — start NVDA
|
|
381
|
+
3. Navigate to ${url} in browser
|
|
382
|
+
4. \`start_audit\` with url "${url}"
|
|
383
|
+
|
|
384
|
+
## Image Traversal
|
|
385
|
+
5. \`nvda_perform\` → moveToNextGraphic (repeat until exhausted)
|
|
386
|
+
For each graphic:
|
|
387
|
+
a. \`nvda_last_spoken_phrase\` — capture what NVDA announces
|
|
388
|
+
b. \`nvda_item_text\` — get item text
|
|
389
|
+
|
|
390
|
+
## Per-Image Analysis
|
|
391
|
+
Classify each image based on NVDA's announcement:
|
|
392
|
+
- **Has descriptive alt text** → "graphic, [description]" → PASS
|
|
393
|
+
- **Empty alt (decorative)** → NVDA skips entirely or announces just role → PASS (verify intentional)
|
|
394
|
+
- **Missing alt** → NVDA announces filename or "graphic" with no description → VIOLATION
|
|
395
|
+
- **Alt text is filename** → e.g. "image001.jpg" → VIOLATION
|
|
396
|
+
- **Alt text is redundant** → same as surrounding text → WARNING
|
|
397
|
+
- **Alt text says "image of"/"picture of"** → redundant prefix → WARNING
|
|
398
|
+
|
|
399
|
+
For complex images (charts, graphs, infographics):
|
|
400
|
+
\`nvda_perform\` → openLongDescription — check if long description exists and is comprehensive
|
|
401
|
+
Verify the image has an aria-describedby, figure/figcaption, or linked long description
|
|
402
|
+
|
|
403
|
+
## Verification Checklist
|
|
404
|
+
- All informative images have meaningful alt text (WCAG 1.1.1)
|
|
405
|
+
- Decorative images are hidden from the accessibility tree (alt="" or role="presentation")
|
|
406
|
+
- Complex images have complete long descriptions
|
|
407
|
+
|
|
408
|
+
6. \`log_finding\` per image with issue
|
|
409
|
+
|
|
410
|
+
## Wrap-up
|
|
411
|
+
7. \`end_audit\`
|
|
412
|
+
8. \`nvda_stop\` — stop NVDA
|
|
413
|
+
9. \`generate_report\` with format "markdown"`,
|
|
414
|
+
},
|
|
415
|
+
},
|
|
416
|
+
];
|
|
417
|
+
},
|
|
418
|
+
};
|
|
419
|
+
const PROMPT_DEFINITIONS = new Map([
|
|
420
|
+
['full_accessibility_audit', fullAccessibilityAudit],
|
|
421
|
+
['heading_structure_audit', headingStructureAudit],
|
|
422
|
+
['form_accessibility_audit', formAccessibilityAudit],
|
|
423
|
+
['navigation_audit', navigationAudit],
|
|
424
|
+
['image_accessibility_audit', imageAudit],
|
|
425
|
+
]);
|
|
426
|
+
export const listNvdaPrompts = () => listPrompts(PROMPT_DEFINITIONS);
|
|
427
|
+
export const getNvdaPromptMessages = (name, args) => getPromptMessages(name, args, PROMPT_DEFINITIONS);
|
|
428
|
+
//# sourceMappingURL=audit-prompts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit-prompts.js","sourceRoot":"","sources":["../../src/prompts/audit-prompts.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ClickOptions, ScreenReaderCommandResult, ScreenReaderPort } from '@weaaare/mcp-auditor-core';
|
|
2
|
+
export declare class NvdaAdapter implements ScreenReaderPort {
|
|
3
|
+
start(): Promise<ScreenReaderCommandResult>;
|
|
4
|
+
stop(): Promise<ScreenReaderCommandResult>;
|
|
5
|
+
next(): Promise<ScreenReaderCommandResult>;
|
|
6
|
+
previous(): Promise<ScreenReaderCommandResult>;
|
|
7
|
+
act(): Promise<ScreenReaderCommandResult>;
|
|
8
|
+
interact(): Promise<ScreenReaderCommandResult>;
|
|
9
|
+
stopInteracting(): Promise<ScreenReaderCommandResult>;
|
|
10
|
+
press(key: string): Promise<ScreenReaderCommandResult>;
|
|
11
|
+
type(text: string): Promise<ScreenReaderCommandResult>;
|
|
12
|
+
perform(command: string): Promise<ScreenReaderCommandResult>;
|
|
13
|
+
performCommander(command: string): Promise<ScreenReaderCommandResult>;
|
|
14
|
+
getCommanderCommands(): string[];
|
|
15
|
+
itemText(): Promise<string>;
|
|
16
|
+
lastSpokenPhrase(): Promise<string>;
|
|
17
|
+
spokenPhraseLog(): Promise<string[]>;
|
|
18
|
+
itemTextLog(): Promise<string[]>;
|
|
19
|
+
clearSpokenPhraseLog(): Promise<void>;
|
|
20
|
+
clearItemTextLog(): Promise<void>;
|
|
21
|
+
click(options?: ClickOptions): Promise<ScreenReaderCommandResult>;
|
|
22
|
+
detect(): Promise<boolean>;
|
|
23
|
+
isDefault(): Promise<boolean>;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=nvda.adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nvda.adapter.d.ts","sourceRoot":"","sources":["../../../src/screen-readers/nvda/nvda.adapter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,YAAY,EACZ,yBAAyB,EACzB,gBAAgB,EACjB,MAAM,2BAA2B,CAAC;AAuHnC,qBAAa,WAAY,YAAW,gBAAgB;IAC5C,KAAK,IAAI,OAAO,CAAC,yBAAyB,CAAC;IAK3C,IAAI,IAAI,OAAO,CAAC,yBAAyB,CAAC;IAK1C,IAAI,IAAI,OAAO,CAAC,yBAAyB,CAAC;IAO1C,QAAQ,IAAI,OAAO,CAAC,yBAAyB,CAAC;IAO9C,GAAG,IAAI,OAAO,CAAC,yBAAyB,CAAC;IAMzC,QAAQ,IAAI,OAAO,CAAC,yBAAyB,CAAC;IAM9C,eAAe,IAAI,OAAO,CAAC,yBAAyB,CAAC;IAMrD,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,yBAAyB,CAAC;IAMtD,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,yBAAyB,CAAC;IAKtD,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,yBAAyB,CAAC;IAgB5D,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,yBAAyB,CAAC;IAM3E,oBAAoB,IAAI,MAAM,EAAE;IAI1B,QAAQ,IAAI,OAAO,CAAC,MAAM,CAAC;IAI3B,gBAAgB,IAAI,OAAO,CAAC,MAAM,CAAC;IAInC,eAAe,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAIpC,WAAW,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAIhC,oBAAoB,IAAI,OAAO,CAAC,IAAI,CAAC;IAIrC,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IAIjC,KAAK,CAAC,OAAO,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,yBAAyB,CAAC;IAejE,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC;IAI1B,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC;CAGpC"}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { nvda } from '@guidepup/guidepup';
|
|
2
|
+
const buildCommandMap = () => {
|
|
3
|
+
const kc = nvda.keyboardCommands;
|
|
4
|
+
return {
|
|
5
|
+
// ── Basic navigation ──────────────────────────────────────────────────────
|
|
6
|
+
moveToNext: kc.moveToNext,
|
|
7
|
+
moveToPrevious: kc.moveToPrevious,
|
|
8
|
+
readNextFocusableItem: kc.readNextFocusableItem,
|
|
9
|
+
performDefaultActionForItem: kc.performDefaultActionForItem,
|
|
10
|
+
activate: kc.activate,
|
|
11
|
+
// ── Browse mode — Headings ────────────────────────────────────────────────
|
|
12
|
+
moveToNextHeading: kc.moveToNextHeading,
|
|
13
|
+
moveToPreviousHeading: kc.moveToPreviousHeading,
|
|
14
|
+
moveToNextHeadingLevel1: kc.moveToNextHeadingLevel1,
|
|
15
|
+
moveToPreviousHeadingLevel1: kc.moveToPreviousHeadingLevel1,
|
|
16
|
+
moveToNextHeadingLevel2: kc.moveToNextHeadingLevel2,
|
|
17
|
+
moveToPreviousHeadingLevel2: kc.moveToPreviousHeadingLevel2,
|
|
18
|
+
moveToNextHeadingLevel3: kc.moveToNextHeadingLevel3,
|
|
19
|
+
moveToPreviousHeadingLevel3: kc.moveToPreviousHeadingLevel3,
|
|
20
|
+
moveToNextHeadingLevel4: kc.moveToNextHeadingLevel4,
|
|
21
|
+
moveToPreviousHeadingLevel4: kc.moveToPreviousHeadingLevel4,
|
|
22
|
+
moveToNextHeadingLevel5: kc.moveToNextHeadingLevel5,
|
|
23
|
+
moveToPreviousHeadingLevel5: kc.moveToPreviousHeadingLevel5,
|
|
24
|
+
moveToNextHeadingLevel6: kc.moveToNextHeadingLevel6,
|
|
25
|
+
moveToPreviousHeadingLevel6: kc.moveToPreviousHeadingLevel6,
|
|
26
|
+
// ── Browse mode — Links ───────────────────────────────────────────────────
|
|
27
|
+
moveToNextLink: kc.moveToNextLink,
|
|
28
|
+
moveToPreviousLink: kc.moveToPreviousLink,
|
|
29
|
+
moveToNextUnvisitedLink: kc.moveToNextUnvisitedLink,
|
|
30
|
+
moveToPreviousUnvisitedLink: kc.moveToPreviousUnvisitedLink,
|
|
31
|
+
moveToNextVisitedLink: kc.moveToNextVisitedLink,
|
|
32
|
+
moveToPreviousVisitedLink: kc.moveToPreviousVisitedLink,
|
|
33
|
+
// ── Browse mode — Forms ───────────────────────────────────────────────────
|
|
34
|
+
moveToNextFormField: kc.moveToNextFormField,
|
|
35
|
+
moveToPreviousFormField: kc.moveToPreviousFormField,
|
|
36
|
+
moveToNextButton: kc.moveToNextButton,
|
|
37
|
+
moveToPreviousButton: kc.moveToPreviousButton,
|
|
38
|
+
moveToNextCheckbox: kc.moveToNextCheckbox,
|
|
39
|
+
moveToPreviousCheckbox: kc.moveToPreviousCheckbox,
|
|
40
|
+
moveToNextComboBox: kc.moveToNextComboBox,
|
|
41
|
+
moveToPreviousComboBox: kc.moveToPreviousComboBox,
|
|
42
|
+
moveToNextRadioButton: kc.moveToNextRadioButton,
|
|
43
|
+
moveToPreviousRadioButton: kc.moveToPreviousRadioButton,
|
|
44
|
+
moveToNextEditField: kc.moveToNextEditField,
|
|
45
|
+
moveToPreviousEditField: kc.moveToPreviousEditField,
|
|
46
|
+
// ── Browse mode — Landmarks ───────────────────────────────────────────────
|
|
47
|
+
moveToNextLandmark: kc.moveToNextLandmark,
|
|
48
|
+
moveToPreviousLandmark: kc.moveToPreviousLandmark,
|
|
49
|
+
// ── Browse mode — Tables ──────────────────────────────────────────────────
|
|
50
|
+
moveToNextTable: kc.moveToNextTable,
|
|
51
|
+
moveToPreviousTable: kc.moveToPreviousTable,
|
|
52
|
+
moveToNextColumn: kc.moveToNextColumn,
|
|
53
|
+
moveToPreviousColumn: kc.moveToPreviousColumn,
|
|
54
|
+
moveToNextRow: kc.moveToNextRow,
|
|
55
|
+
moveToPreviousRow: kc.moveToPreviousRow,
|
|
56
|
+
// ── Browse mode — Lists ───────────────────────────────────────────────────
|
|
57
|
+
moveToNextList: kc.moveToNextList,
|
|
58
|
+
moveToPreviousList: kc.moveToPreviousList,
|
|
59
|
+
moveToNextListItem: kc.moveToNextListItem,
|
|
60
|
+
moveToPreviousListItem: kc.moveToPreviousListItem,
|
|
61
|
+
// ── Browse mode — Other elements ──────────────────────────────────────────
|
|
62
|
+
moveToNextGraphic: kc.moveToNextGraphic,
|
|
63
|
+
moveToPreviousGraphic: kc.moveToPreviousGraphic,
|
|
64
|
+
moveToNextBlockQuote: kc.moveToNextBlockQuote,
|
|
65
|
+
moveToPreviousBlockQuote: kc.moveToPreviousBlockQuote,
|
|
66
|
+
moveToNextNonLinkedText: kc.moveToNextNonLinkedText,
|
|
67
|
+
moveToPreviousNonLinkedText: kc.moveToPreviousNonLinkedText,
|
|
68
|
+
moveToNextSeparator: kc.moveToNextSeparator,
|
|
69
|
+
moveToPreviousSeparator: kc.moveToPreviousSeparator,
|
|
70
|
+
moveToNextFrame: kc.moveToNextFrame,
|
|
71
|
+
moveToPreviousFrame: kc.moveToPreviousFrame,
|
|
72
|
+
moveToNextAnnotation: kc.moveToNextAnnotation,
|
|
73
|
+
moveToPreviousAnnotation: kc.moveToPreviousAnnotation,
|
|
74
|
+
moveToNextSpellingError: kc.moveToNextSpellingError,
|
|
75
|
+
moveToPreviousSpellingError: kc.moveToPreviousSpellingError,
|
|
76
|
+
moveToNextEmbeddedObject: kc.moveToNextEmbeddedObject,
|
|
77
|
+
moveToPreviousEmbeddedObject: kc.moveToPreviousEmbeddedObject,
|
|
78
|
+
moveToStartOfContainer: kc.moveToStartOfContainer,
|
|
79
|
+
movePastEndOfContainer: kc.movePastEndOfContainer,
|
|
80
|
+
// ── Browse mode — Mode control ────────────────────────────────────────────
|
|
81
|
+
toggleBetweenBrowseAndFocusMode: kc.toggleBetweenBrowseAndFocusMode,
|
|
82
|
+
exitFocusMode: kc.exitFocusMode,
|
|
83
|
+
browseModeElementsList: kc.browseModeElementsList,
|
|
84
|
+
toggleSingleLetterNavigation: kc.toggleSingleLetterNavigation,
|
|
85
|
+
refreshBrowseDocument: kc.refreshBrowseDocument,
|
|
86
|
+
// ── Reading and reporting ─────────────────────────────────────────────────
|
|
87
|
+
sayAll: kc.sayAll,
|
|
88
|
+
readLine: kc.readLine,
|
|
89
|
+
readCurrentSelection: kc.readCurrentSelection,
|
|
90
|
+
reportTextFormatting: kc.reportTextFormatting,
|
|
91
|
+
reportTitle: kc.reportTitle,
|
|
92
|
+
reportCurrentFocus: kc.reportCurrentFocus,
|
|
93
|
+
readActiveWindow: kc.readActiveWindow,
|
|
94
|
+
reportStatusBar: kc.reportStatusBar,
|
|
95
|
+
reportDateTime: kc.reportDateTime,
|
|
96
|
+
reportClipboardText: kc.reportClipboardText,
|
|
97
|
+
// ── Find ──────────────────────────────────────────────────────────────────
|
|
98
|
+
find: kc.find,
|
|
99
|
+
findNext: kc.findNext,
|
|
100
|
+
findPrevious: kc.findPrevious,
|
|
101
|
+
openLongDescription: kc.openLongDescription,
|
|
102
|
+
// ── Speech control ────────────────────────────────────────────────────────
|
|
103
|
+
stopSpeech: kc.stopSpeech,
|
|
104
|
+
pauseSpeech: kc.pauseSpeech,
|
|
105
|
+
};
|
|
106
|
+
};
|
|
107
|
+
export class NvdaAdapter {
|
|
108
|
+
async start() {
|
|
109
|
+
await nvda.start();
|
|
110
|
+
return { action: 'NVDA started successfully' };
|
|
111
|
+
}
|
|
112
|
+
async stop() {
|
|
113
|
+
await nvda.stop();
|
|
114
|
+
return { action: 'NVDA stopped successfully' };
|
|
115
|
+
}
|
|
116
|
+
async next() {
|
|
117
|
+
await nvda.next();
|
|
118
|
+
const itemText = await nvda.itemText();
|
|
119
|
+
const spokenPhrase = await nvda.lastSpokenPhrase();
|
|
120
|
+
return { action: 'moved to next item', itemText, spokenPhrase };
|
|
121
|
+
}
|
|
122
|
+
async previous() {
|
|
123
|
+
await nvda.previous();
|
|
124
|
+
const itemText = await nvda.itemText();
|
|
125
|
+
const spokenPhrase = await nvda.lastSpokenPhrase();
|
|
126
|
+
return { action: 'moved to previous item', itemText, spokenPhrase };
|
|
127
|
+
}
|
|
128
|
+
async act() {
|
|
129
|
+
await nvda.act();
|
|
130
|
+
const spokenPhrase = await nvda.lastSpokenPhrase();
|
|
131
|
+
return { action: 'performed default action', spokenPhrase };
|
|
132
|
+
}
|
|
133
|
+
async interact() {
|
|
134
|
+
await nvda.interact();
|
|
135
|
+
const spokenPhrase = await nvda.lastSpokenPhrase();
|
|
136
|
+
return { action: 'interact (no-op on NVDA)', spokenPhrase };
|
|
137
|
+
}
|
|
138
|
+
async stopInteracting() {
|
|
139
|
+
await nvda.stopInteracting();
|
|
140
|
+
const spokenPhrase = await nvda.lastSpokenPhrase();
|
|
141
|
+
return { action: 'stopInteracting (no-op on NVDA)', spokenPhrase };
|
|
142
|
+
}
|
|
143
|
+
async press(key) {
|
|
144
|
+
await nvda.press(key);
|
|
145
|
+
const spokenPhrase = await nvda.lastSpokenPhrase();
|
|
146
|
+
return { action: `pressed key: ${key}`, spokenPhrase };
|
|
147
|
+
}
|
|
148
|
+
async type(text) {
|
|
149
|
+
await nvda.type(text);
|
|
150
|
+
return { action: `typed: ${text}` };
|
|
151
|
+
}
|
|
152
|
+
async perform(command) {
|
|
153
|
+
const commandMap = buildCommandMap();
|
|
154
|
+
const keyboardCommand = commandMap[command];
|
|
155
|
+
if (!keyboardCommand) {
|
|
156
|
+
throw new Error(`Unknown command: ${command}. Available: ${Object.keys(commandMap).join(', ')}`);
|
|
157
|
+
}
|
|
158
|
+
await nvda.perform(keyboardCommand);
|
|
159
|
+
const itemText = await nvda.itemText();
|
|
160
|
+
const spokenPhrase = await nvda.lastSpokenPhrase();
|
|
161
|
+
return { action: `performed command: ${command}`, itemText, spokenPhrase };
|
|
162
|
+
}
|
|
163
|
+
async performCommander(command) {
|
|
164
|
+
// NVDA has no separate "commander" channel — keyboard commands are the only API.
|
|
165
|
+
// This method exists for ScreenReaderPort compatibility.
|
|
166
|
+
return this.perform(command);
|
|
167
|
+
}
|
|
168
|
+
getCommanderCommands() {
|
|
169
|
+
return Object.keys(buildCommandMap());
|
|
170
|
+
}
|
|
171
|
+
async itemText() {
|
|
172
|
+
return nvda.itemText();
|
|
173
|
+
}
|
|
174
|
+
async lastSpokenPhrase() {
|
|
175
|
+
return nvda.lastSpokenPhrase();
|
|
176
|
+
}
|
|
177
|
+
async spokenPhraseLog() {
|
|
178
|
+
return nvda.spokenPhraseLog();
|
|
179
|
+
}
|
|
180
|
+
async itemTextLog() {
|
|
181
|
+
return nvda.itemTextLog();
|
|
182
|
+
}
|
|
183
|
+
async clearSpokenPhraseLog() {
|
|
184
|
+
await nvda.clearSpokenPhraseLog();
|
|
185
|
+
}
|
|
186
|
+
async clearItemTextLog() {
|
|
187
|
+
await nvda.clearItemTextLog();
|
|
188
|
+
}
|
|
189
|
+
async click(options) {
|
|
190
|
+
const button = options?.button ?? 'left';
|
|
191
|
+
const clickCount = options?.clickCount ?? 1;
|
|
192
|
+
if (![1, 2, 3].includes(clickCount)) {
|
|
193
|
+
throw new Error('clickCount must be 1, 2, or 3.');
|
|
194
|
+
}
|
|
195
|
+
await nvda.click({ button, clickCount: clickCount });
|
|
196
|
+
return {
|
|
197
|
+
action: 'clicked mouse',
|
|
198
|
+
spokenPhrase: `${button} click x${clickCount}`,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
async detect() {
|
|
202
|
+
return nvda.detect();
|
|
203
|
+
}
|
|
204
|
+
async isDefault() {
|
|
205
|
+
return nvda.default();
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
//# sourceMappingURL=nvda.adapter.js.map
|