From b7eae16c807f2f2ebefeb25974440fcf9d638234 Mon Sep 17 00:00:00 2001 From: Aaryan Choudhary Date: Tue, 10 Nov 2026 12:22:20 +0530 Subject: [PATCH 1/4] feat(accessibility): add a fix-accessibility-issues prompt A prompt the user starts on purpose (/mcp__browserstack__fix-accessibility-issues in Claude Code, /mcp.browserstack.fix-accessibility-issues in Copilot). It hands their coding agent BrowserStack's fix workflow: agree the scope, get the issues, triage and show a plan, fix one issue at a time, verify with a re-run and a compare, and report. Optional target and scope arguments fill in what to check and what to fix. Co-Authored-By: Claude Opus 5.5 --- src/server-factory.ts | 5 +- src/tools/accessibility.ts | 42 ++++++ src/tools/accessiblity-utils/fix-workflow.ts | 142 +++++++++++++++++++ tests/tools/accessibilityFixWorkflow.test.ts | 63 ++++++++ 4 files changed, 251 insertions(+), 1 deletion(-) create mode 100644 src/tools/accessiblity-utils/fix-workflow.ts create mode 100644 tests/tools/accessibilityFixWorkflow.test.ts 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..f2c84e8a --- /dev/null +++ b/src/tools/accessiblity-utils/fix-workflow.ts @@ -0,0 +1,142 @@ +/* + * 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. You work in this order: **scope → get the issues → triage → fix one issue at a time → verify with BrowserStack → report.** + +Before you act, read this whole workflow. Do the steps in order and don't skip any. Where a step says to state something, write it in your reply before you make the change. + +## Step 1: Agree the scope + +You need two things. If either is unclear, ask the user once, in one message. Don't guess. + +1. **What to check.** One of: + - a page URL, public or on a local dev server (\`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; + - one component or section of a page (note its CSS selector, or describe it). +2. **What to fix:** the whole page, one component, critical issues only, or only issues in code changed on this branch. + +Then find out how the app runs: the framework, the dev-server command, and whether it hot-reloads or needs a rebuild after a change. You need the page served with your changes to verify them. + +## Step 2: 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 an earlier scan of this exact page is in this conversation): re-run that scan with \`startAccessibilityScan\` and its \`scanId\`. Don't create a new scan. Runs of one scan can be compared; runs of two different scans can't. +- **Otherwise:** start a scan with \`startAccessibilityScan\`, giving a short \`name\` and the \`pageURL\`. + - **Local URL:** the local MCP opens a BrowserStack Local tunnel itself. The hosted MCP can't: if it refuses, ask the user to start BrowserStack Local and pass the tunnel's local identifier as \`localIdentifier\`. + - **Page behind a login:** create a login config with \`createAccessibilityAuthConfig\` and pass its id as \`authConfigId\`. Never ask for a password in chat. + +Keep the \`scanId\`, the \`scanRunId\` and the dashboard link: you need them to verify and to report. Read every page of results, using the \`cursor\` each answer gives, until you have every issue in scope, not only the first page. + +## Step 3: Triage before you touch code + +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. **Set "needs review" issues aside.** An issue with \`needs_review\` set is one the engine could not decide. Don't fix it unless you confirm it in the code or the user asks. List these separately. +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, even if it shows up many times: a shared \`\` without a label can produce 40 occurrences. +5. **Order the work:** + 1. Issues that make an interactive element unusable: a wrong or missing **role**, no accessible **name**, or wrong **state**; nested interactive controls; and **keyboard** issues (can't focus, can't operate, focus trap, broken focus order). + 2. Then by severity: **critical → serious → moderate → minor**. + 3. Within the same level, root causes with the most occurrences first. +6. **Show the plan and wait.** Give a table: #, rule, severity, element, source file and component (mark shared components), occurrences, planned change. Ask which to fix, 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 all critical issues in scope. + +## Step 4: Fix one issue at a time + +Take the next issue in the plan and do all of the following before moving on. **Never batch fixes, and never fix two issues in one edit.** + +1. **Find the source.** Find the file and component that render the element. 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. **Third-party code: stop.** If the element comes from an external library, don't modify, wrap, re-implement or pass new props (including \`aria-*\`) to it. Report this block and move on to the next issue: + + \`\`\` + FLAGGED_EXTERNAL_LIBRARY_ISSUE + Component/package: + Issue: + Impact: + Action taken: not fixed; this is third-party code. Flagged for the user. + \`\`\` +3. **Shared components: ask first.** If the fix belongs in a component used across the app (a design system, or a shared UI package in a monorepo), stop. Say which component it is, roughly where it is used, and what you would change, then ask the user before editing it. A "go" to a plan that named the shared component and the change counts as approval; a shared component the plan didn't name needs its own question. +4. **Identify the pattern** (required for role, name, state and keyboard issues). Decide which ARIA Authoring Practices (APG) pattern the element implements: accordion, alert, breadcrumb, button, carousel, checkbox, combobox, dialog (modal), disclosure, feed, grid, landmarks, link, listbox, menu, menu button, meter, radio group, slider, spinbutton, switch, tabs, table, toolbar, tooltip, tree view, treegrid, window splitter. + - Decide from the component's code first, then the live DOM, and ask the user only as a last resort. + - Prefer a native HTML element (\`