Skip to content

Refactor Accessibility Scan Tools with Enhanced Progress Updates and Local Report Download #41

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
13 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 73 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,11 @@
"author": "",
"license": "ISC",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.10.1",
"@modelcontextprotocol/sdk": "^1.11.4",
"@types/form-data": "^2.5.2",
"axios": "^1.8.4",
"browserstack-local": "^1.5.6",
"csv-parse": "^5.6.0",
"dotenv": "^16.5.0",
"form-data": "^4.0.2",
"pino": "^9.6.0",
Expand All @@ -49,6 +50,7 @@
},
"devDependencies": {
"@eslint/js": "^9.25.0",
"@types/csv-parse": "^1.1.12",
"@types/node": "^22.14.1",
"@types/uuid": "^10.0.0",
"eslint": "^9.25.0",
Expand Down
70 changes: 51 additions & 19 deletions src/tools/accessibility.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,64 @@
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
import {
startAccessibilityScan,
AccessibilityScanResponse,
} from "./accessiblity-utils/accessibility.js";
import { AccessibilityScanner } from "./accessiblity-utils/scanner.js";
import { AccessibilityReportFetcher } from "./accessiblity-utils/report-fetcher.js";
import { trackMCP } from "../lib/instrumentation.js";
import { parseAccessibilityReportFromCSV } from "./accessiblity-utils/report-parser.js";

const scanner = new AccessibilityScanner();
const reportFetcher = new AccessibilityReportFetcher();

async function runAccessibilityScan(
name: string,
pageURL: string,
context: any,
): Promise<CallToolResult> {
const response: AccessibilityScanResponse = await startAccessibilityScan(
name,
[pageURL],
);
const scanId = response.data?.id;
const scanRunId = response.data?.scanRunId;
// Start scan
const startResp = await scanner.startScan(name, [pageURL]);
const scanId = startResp.data!.id;
const scanRunId = startResp.data!.scanRunId;

if (!scanId || !scanRunId) {
throw new Error(
"Unable to start a accessibility scan, please try again later or open an issue on GitHub if the problem persists",
);
// Notify scan start
await context.sendNotification({
method: "notifications/progress",
params: {
progressToken: context._meta?.progressToken ?? "NOT_FOUND",
message: `Accessibility scan "${name}" started`,
progress: 0,
total: 100,
},
});

// Wait until scan completes
const status = await scanner.waitUntilComplete(scanId, scanRunId, context);
if (status !== "completed") {
return {
content: [
{
type: "text",
text: `❌ Accessibility scan "${name}" failed with status: ${status} , check the BrowserStack dashboard for more details [https://scanner.browserstack.com/site-scanner/scan-details/${name}].`,
isError: true,
},
],
isError: true,
};
}

// Fetch CSV report link
const reportLink = await reportFetcher.getReportLink(scanId, scanRunId);

const { records } = await parseAccessibilityReportFromCSV(reportLink);

return {
content: [
{
type: "text",
text: `Successfully queued accessibility scan, you will get a report via email within 5 minutes.`,
text: `✅ Accessibility scan "${name}" completed. check the BrowserStack dashboard for more details [https://scanner.browserstack.com/site-scanner/scan-details/${name}].`,
},
{
type: "text",
text: `Scan results: ${JSON.stringify(records, null, 2)}`,
},
],
};
Expand All @@ -37,15 +67,15 @@ async function runAccessibilityScan(
export default function addAccessibilityTools(server: McpServer) {
server.tool(
"startAccessibilityScan",
"Use this tool to start an accessibility scan for a list of URLs on BrowserStack.",
"Start an accessibility scan via BrowserStack and retrieve a local CSV report path.",
{
name: z.string().describe("Name of the accessibility scan"),
pageURL: z.string().describe("The URL to scan for accessibility issues"),
},
async (args) => {
async (args, context) => {
try {
trackMCP("startAccessibilityScan", server.server.getClientVersion()!);
return await runAccessibilityScan(args.name, args.pageURL);
return await runAccessibilityScan(args.name, args.pageURL, context);
} catch (error) {
trackMCP(
"startAccessibilityScan",
Expand All @@ -56,7 +86,9 @@ export default function addAccessibilityTools(server: McpServer) {
content: [
{
type: "text",
text: `Failed to start accessibility scan: ${error instanceof Error ? error.message : "Unknown error"}. Please open an issue on GitHub if the problem persists`,
text: `Failed to start accessibility scan: ${
error instanceof Error ? error.message : "Unknown error"
}. Please open an issue on GitHub if the problem persists`,
isError: true,
},
],
Expand Down
Loading