diff --git a/src/server-factory.ts b/src/server-factory.ts index e04f0791..847d2653 100644 --- a/src/server-factory.ts +++ b/src/server-factory.ts @@ -9,7 +9,9 @@ import logger from "./logger.js"; import addSDKTools from "./tools/bstack-sdk.js"; import addPercyTools from "./tools/percy-sdk.js"; import addBrowserLiveTools from "./tools/live.js"; -import addAccessibilityTools from "./tools/accessibility.js"; +import addAccessibilityTools, { + addAccessibilityFixWorkflow, +} from "./tools/accessibility.js"; import addTestManagementTools from "./tools/testmanagement.js"; import addAppAutomationTools from "./tools/appautomate.js"; import addFailureLogsTools from "./tools/get-failure-logs.js"; @@ -76,6 +78,7 @@ export class BrowserStackMcpServer { setupOnInitialized(this.server, this.config); this.registerTools(); + addAccessibilityFixWorkflow(this.server); } /** diff --git a/src/tools/accessibility.ts b/src/tools/accessibility.ts index 82dc1449..7ab335f1 100644 --- a/src/tools/accessibility.ts +++ b/src/tools/accessibility.ts @@ -1,5 +1,10 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { z } from "zod"; +import { + FIX_WORKFLOW_NAME, + FIX_WORKFLOW_TITLE, + fixWorkflowText, +} from "./accessiblity-utils/fix-workflow.js"; import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; import { AccessibilityScanner, @@ -1918,3 +1923,40 @@ export default function addAccessibilityTools( return tools; } + +/** + * The fix workflow, for a server that lists the accessibility tools: a prompt the user starts + * on purpose, filled in with what they asked to check and to fix. + */ +export function addAccessibilityFixWorkflow(server: McpServer): void { + server.registerPrompt( + FIX_WORKFLOW_NAME, + { + title: FIX_WORKFLOW_TITLE, + description: + "Find, fix and verify accessibility issues in this codebase with BrowserStack.", + argsSchema: { + target: z + .string() + .optional() + .describe( + "What to check: a page URL, a local dev URL, or a BrowserStack report link.", + ), + scope: z + .string() + .optional() + .describe( + "What to fix: e.g. critical issues only, one component, or files changed on this branch.", + ), + }, + }, + ({ target, scope }) => ({ + messages: [ + { + role: "user", + content: { type: "text", text: fixWorkflowText({ target, scope }) }, + }, + ], + }), + ); +} diff --git a/src/tools/accessiblity-utils/fix-workflow.ts b/src/tools/accessiblity-utils/fix-workflow.ts new file mode 100644 index 00000000..9570b36e --- /dev/null +++ b/src/tools/accessiblity-utils/fix-workflow.ts @@ -0,0 +1,159 @@ +/* + * The accessibility fix workflow: how a coding agent finds, fixes and verifies issues with + * BrowserStack. It ships as the prompt fix-accessibility-issues, which a user starts on + * purpose. The text is a string here because the build copies no .md files. + */ + +/** The prompt's name. */ +export const FIX_WORKFLOW_NAME = "fix-accessibility-issues"; + +export const FIX_WORKFLOW_TITLE = "Fix accessibility issues"; + +const WORKFLOW = `You are fixing accessibility issues in this codebase with BrowserStack Accessibility, through the BrowserStack MCP tools. Work in one loop: **detect → analyze → fix → verify**, then report. Each stage has a gate. Don't move to the next stage until the gate is met. + +## Stage 1: Detect + +Find the accessibility issues on the page or component in scope. + +**Gate: you know the target, the scope, and you have every issue in scope from one BrowserStack report.** + +1. **Agree the target and scope.** If either is unclear, ask the user once, in one message. + - **Target:** a page URL (public or local, such as \`http://localhost:3000/...\`); an existing BrowserStack report (a dashboard link, a Website Scanner \`scanId\` and \`scanRunId\`, an Automated Tests \`buildId\`, or a Workflow Analyzer report); or one component or section of a page (its CSS selector, or a description). + - **Scope:** the whole page, one component, critical issues only, or only issues in code changed on this branch. +2. **Learn how the app runs:** the framework, the dev-server command, and whether a change hot-reloads or needs a rebuild. Stage 4 needs the page served with your changes. +3. **Get the issues:** + - **The user gave a report:** read it with \`fetchAccessibilityIssues\` (pass a link as \`reportUrl\`). Don't start a new scan. + - **The page was scanned before** (the user gave a \`scanId\`, or this conversation has an earlier scan of this exact page): re-run that scan with \`startAccessibilityScan\` and its \`scanId\`. Runs of one scan can be compared; runs of two different scans can't. + - **Otherwise:** start a scan with \`startAccessibilityScan\`, with a short \`name\` and the \`pageURL\`. + - **Local URL:** the local MCP opens a BrowserStack Local tunnel itself. The hosted MCP can't. Ask the user to start BrowserStack Local and pass its local identifier as \`localIdentifier\`. If the scan can't reach the local URL, offer two routes: run the page in an Automate test through BrowserStack Local, or scan a public preview URL. + - **Page behind a login:** create a login config with \`createAccessibilityAuthConfig\` and pass its id as \`authConfigId\`. Never ask for a password in chat. +4. Keep the \`scanId\`, the \`scanRunId\` and the dashboard link. Read every page of results with the \`cursor\` each answer gives, until you have every issue in scope. + +## Stage 2: Analyze + +Understand each issue and decide what matters most. + +**Gate: the user has approved a fix plan.** + +1. **Keep only issues in scope:** the right page (\`affected_page\`, or \`screen\` in app reports), the right component or section, and the right severities. +2. **Sort each issue into one of three groups:** + - **Needs review:** the issue has \`needs_review\` set. The engine could not decide it, and a person has not confirmed it. Don't fix it unless you confirm it in the code or the user asks. List these separately. + - **Confirmed WCAG failure:** no \`needs_review\`, and the rule is not a best practice (\`best_practice\` under \`rules\`). + - **Best practice:** the rule is marked \`best_practice\`. Fix these after confirmed failures, and say they are not WCAG failures. +3. **Confirm each issue in the source.** Find the element from its \`component\` (selector) and \`HTML_snippet\` (in app reports, \`screen\` and \`element\`). If the code shows the issue is not real (for example, a skip link whose target does exist), mark it **possible false positive** with a one-line reason and leave the code alone. +4. **Group by root cause.** One rule on one source component is one fix, however many times it shows up. A shared \`\` without a label can produce 40 occurrences. +5. **Say what kind of change each fix is.** This decides what needs approval: + - **Code only:** markup or attributes; no change to how it looks or works. Can go ahead once the plan is approved. + - **Needs content:** alt text, a label, link text or other words only the user can supply. Ask for the words. Never invent them. + - **Changes look or behaviour:** colours, focus styles, layout, or how a control responds. Name the change in the plan. + - **Shared component:** a design-system or shared UI component used across the app. Name the component, roughly where it is used, and the change. + - **Third-party:** the element comes from an external package. Leave third-party code unchanged. Report the package name, and what the user could ask its owners for. +6. **Order the work:** critical → serious → moderate → minor. Within a level, confirmed failures before best practices, then the root causes with the most occurrences first. +7. **Show the plan and wait.** Give a table with these columns: #, rule, severity, group (confirmed / best practice), element, source file and component, occurrences, kind of change, planned change. Then stop, even if the user already asked you to fix everything. Their reply to the plan is the go-ahead. If they say "go", fix every confirmed critical issue in scope. Approval covers only what the plan names. A shared component or a look-or-behaviour change that the plan didn't name needs its own question. + +### Questions about the report: use Ask BrowserStack AI + +First, use what the issue data already gives you. Each issue names its rule (\`issue_type\`). Under \`rules\` you'll find the rule's name, description, WCAG criteria and usual fix. An issue has its own \`how_to_fix\` only when its fix is different from the usual one. + +When that data doesn't answer your question, ask BrowserStack AI instead of guessing. Call \`askBrowserStackAI\` with \`product: "a11y"\`, one question, and the report (its link or ids). For example: +- why a rule fired on this element, or what the rule checks; +- which fix clears the most issues, or what to fix first to raise the score; +- why the report scored the way it did; +- what a field or value in the report means. + +Keep these calls few and focused. Ask only what the issue data can't answer, and ask one clear question per call. Don't call it once per issue: put related questions about the same rule into one call. If it says Ask AI is not enabled for this account, tell the user and carry on from the issue data. + +For general WCAG or ARIA questions that are not about this report, also ask BrowserStack AI: call \`askBrowserStackAI\` with \`product: "a11y"\` and the question. + +## Stage 3: Fix + +Apply each fix in this project's source, one at a time. + +**Gate: each planned root cause is changed in source, and the page still builds and works.** + +Fix one root cause at a time, in plan order. Finish it before you start the next. + +1. **Edit only this project's source files.** Never edit build output (\`dist/\`, \`build/\`, \`.next/\`, compiled CSS), \`node_modules/\`, vendored or generated files, or lockfiles. +2. **Say what you will change.** Before each edit, write one line: \`Fixing # in : \`. +3. **Base the change on the rule.** The rule's fix in the report (\`how_to_fix\`, or the usual fix under \`rules\`) says what the fix must achieve. The code decides how to achieve it. +4. **Prefer native HTML.** Use \`