@browserstack/mcp-server 1.2.2-beta → 1.2.2-beta.1
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/dist/lib/inmemory-store.d.ts +1 -0
- package/dist/lib/inmemory-store.js +1 -0
- package/dist/lib/instrumentation.js +2 -0
- package/dist/server-factory.js +2 -0
- package/dist/tools/accessibility.js +238 -78
- package/dist/tools/accessiblity-utils/auth-config.d.ts +39 -0
- package/dist/tools/accessiblity-utils/auth-config.js +125 -0
- package/dist/tools/accessiblity-utils/scanner.d.ts +1 -1
- package/dist/tools/accessiblity-utils/scanner.js +2 -1
- package/dist/tools/add-percy-snapshots.d.ts +5 -0
- package/dist/tools/add-percy-snapshots.js +17 -0
- package/dist/tools/bstack-sdk.d.ts +2 -15
- package/dist/tools/bstack-sdk.js +7 -124
- package/dist/tools/list-test-files.d.ts +2 -0
- package/dist/tools/list-test-files.js +33 -0
- package/dist/tools/percy-sdk.d.ts +4 -0
- package/dist/tools/percy-sdk.js +88 -0
- package/dist/tools/percy-snapshot-utils/constants.d.ts +16 -0
- package/dist/tools/percy-snapshot-utils/constants.js +500 -0
- package/dist/tools/percy-snapshot-utils/detect-test-files.d.ts +10 -0
- package/dist/tools/percy-snapshot-utils/detect-test-files.js +194 -0
- package/dist/tools/percy-snapshot-utils/types.d.ts +15 -0
- package/dist/tools/percy-snapshot-utils/utils.d.ts +4 -0
- package/dist/tools/percy-snapshot-utils/utils.js +30 -0
- package/dist/tools/sdk-utils/{commands.d.ts → bstack/commands.d.ts} +1 -1
- package/dist/tools/sdk-utils/bstack/commands.js +88 -0
- package/dist/tools/sdk-utils/bstack/configUtils.d.ts +4 -0
- package/dist/tools/sdk-utils/bstack/configUtils.js +66 -0
- package/dist/tools/sdk-utils/bstack/constants.d.ts +58 -0
- package/dist/tools/sdk-utils/{constants.js → bstack/constants.js} +117 -78
- package/dist/tools/sdk-utils/{constants.d.ts → bstack/frameworks.d.ts} +1 -1
- package/dist/tools/sdk-utils/bstack/frameworks.js +57 -0
- package/dist/tools/sdk-utils/bstack/index.d.ts +4 -0
- package/dist/tools/sdk-utils/bstack/index.js +5 -0
- package/dist/tools/sdk-utils/bstack/sdkHandler.d.ts +4 -0
- package/dist/tools/sdk-utils/bstack/sdkHandler.js +74 -0
- package/dist/tools/sdk-utils/common/constants.d.ts +10 -0
- package/dist/tools/sdk-utils/common/constants.js +86 -0
- package/dist/tools/sdk-utils/common/formatUtils.d.ts +5 -0
- package/dist/tools/sdk-utils/common/formatUtils.js +27 -0
- package/dist/tools/sdk-utils/common/index.d.ts +3 -0
- package/dist/tools/sdk-utils/common/index.js +4 -0
- package/dist/tools/sdk-utils/common/instructionUtils.d.ts +8 -0
- package/dist/tools/sdk-utils/common/instructionUtils.js +20 -0
- package/dist/tools/sdk-utils/common/schema.d.ts +61 -0
- package/dist/tools/sdk-utils/common/schema.js +28 -0
- package/dist/tools/sdk-utils/common/types.d.ts +66 -0
- package/dist/tools/sdk-utils/common/types.js +50 -0
- package/dist/tools/sdk-utils/common/utils.d.ts +25 -0
- package/dist/tools/sdk-utils/common/utils.js +84 -0
- package/dist/tools/sdk-utils/handler.d.ts +5 -0
- package/dist/tools/sdk-utils/handler.js +144 -0
- package/dist/tools/sdk-utils/percy-automate/constants.d.ts +11 -0
- package/dist/tools/sdk-utils/percy-automate/constants.js +365 -0
- package/dist/tools/sdk-utils/percy-automate/frameworks.d.ts +8 -0
- package/dist/tools/sdk-utils/percy-automate/frameworks.js +50 -0
- package/dist/tools/sdk-utils/percy-automate/handler.d.ts +3 -0
- package/dist/tools/sdk-utils/percy-automate/handler.js +30 -0
- package/dist/tools/sdk-utils/percy-automate/index.d.ts +1 -0
- package/dist/tools/sdk-utils/percy-automate/index.js +2 -0
- package/dist/tools/sdk-utils/percy-automate/types.d.ts +13 -0
- package/dist/tools/sdk-utils/percy-automate/types.js +1 -0
- package/dist/tools/sdk-utils/percy-bstack/constants.d.ts +4 -0
- package/dist/tools/sdk-utils/{percy → percy-bstack}/constants.js +13 -39
- package/dist/tools/sdk-utils/percy-bstack/frameworks.d.ts +2 -0
- package/dist/tools/sdk-utils/percy-bstack/frameworks.js +27 -0
- package/dist/tools/sdk-utils/percy-bstack/handler.d.ts +4 -0
- package/dist/tools/sdk-utils/percy-bstack/handler.js +99 -0
- package/dist/tools/sdk-utils/percy-bstack/index.d.ts +4 -0
- package/dist/tools/sdk-utils/percy-bstack/index.js +4 -0
- package/dist/tools/sdk-utils/percy-bstack/instructions.d.ts +7 -0
- package/dist/tools/sdk-utils/{percy → percy-bstack}/instructions.js +5 -9
- package/dist/tools/sdk-utils/percy-bstack/types.d.ts +13 -0
- package/dist/tools/sdk-utils/percy-bstack/types.js +5 -0
- package/dist/tools/sdk-utils/percy-web/constants.d.ts +41 -0
- package/dist/tools/sdk-utils/percy-web/constants.js +941 -0
- package/dist/tools/sdk-utils/percy-web/fetchPercyToken.d.ts +4 -0
- package/dist/tools/sdk-utils/percy-web/fetchPercyToken.js +28 -0
- package/dist/tools/sdk-utils/percy-web/frameworks.d.ts +7 -0
- package/dist/tools/sdk-utils/percy-web/frameworks.js +103 -0
- package/dist/tools/sdk-utils/percy-web/handler.d.ts +4 -0
- package/dist/tools/sdk-utils/percy-web/handler.js +27 -0
- package/dist/tools/sdk-utils/percy-web/index.d.ts +4 -0
- package/dist/tools/sdk-utils/percy-web/index.js +4 -0
- package/dist/tools/sdk-utils/percy-web/types.d.ts +12 -0
- package/dist/tools/sdk-utils/percy-web/types.js +1 -0
- package/package.json +1 -1
- package/dist/tools/sdk-utils/commands.js +0 -65
- package/dist/tools/sdk-utils/instructions.d.ts +0 -6
- package/dist/tools/sdk-utils/instructions.js +0 -99
- package/dist/tools/sdk-utils/percy/constants.d.ts +0 -3
- package/dist/tools/sdk-utils/percy/instructions.d.ts +0 -10
- package/dist/tools/sdk-utils/percy/types.d.ts +0 -5
- /package/dist/tools/{sdk-utils/percy → percy-snapshot-utils}/types.js +0 -0
|
@@ -4,6 +4,7 @@ import { createRequire } from "module";
|
|
|
4
4
|
const require = createRequire(import.meta.url);
|
|
5
5
|
const packageJson = require("../../package.json");
|
|
6
6
|
import axios from "axios";
|
|
7
|
+
import globalConfig from "../config.js";
|
|
7
8
|
export function trackMCP(toolName, clientInfo, error, config) {
|
|
8
9
|
const instrumentationEndpoint = "https://api.browserstack.com/sdk/v1/event";
|
|
9
10
|
const isSuccess = !error;
|
|
@@ -22,6 +23,7 @@ export function trackMCP(toolName, clientInfo, error, config) {
|
|
|
22
23
|
tool_name: toolName,
|
|
23
24
|
mcp_client: mcpClient,
|
|
24
25
|
success: isSuccess,
|
|
26
|
+
is_remote: globalConfig.REMOTE_MCP,
|
|
25
27
|
},
|
|
26
28
|
};
|
|
27
29
|
// Add error details if applicable
|
package/dist/server-factory.js
CHANGED
|
@@ -4,6 +4,7 @@ const require = createRequire(import.meta.url);
|
|
|
4
4
|
const packageJson = require("../package.json");
|
|
5
5
|
import logger from "./logger.js";
|
|
6
6
|
import addSDKTools from "./tools/bstack-sdk.js";
|
|
7
|
+
import addPercyTools from "./tools/percy-sdk.js";
|
|
7
8
|
import addBrowserLiveTools from "./tools/live.js";
|
|
8
9
|
import addAccessibilityTools from "./tools/accessibility.js";
|
|
9
10
|
import addTestManagementTools from "./tools/testmanagement.js";
|
|
@@ -38,6 +39,7 @@ export class BrowserStackMcpServer {
|
|
|
38
39
|
const toolAdders = [
|
|
39
40
|
addAccessibilityTools,
|
|
40
41
|
addSDKTools,
|
|
42
|
+
addPercyTools,
|
|
41
43
|
addAppLiveTools,
|
|
42
44
|
addBrowserLiveTools,
|
|
43
45
|
addTestManagementTools,
|
|
@@ -1,66 +1,211 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { AccessibilityScanner } from "./accessiblity-utils/scanner.js";
|
|
3
3
|
import { AccessibilityReportFetcher } from "./accessiblity-utils/report-fetcher.js";
|
|
4
|
+
import { AccessibilityAuthConfig } from "./accessiblity-utils/auth-config.js";
|
|
4
5
|
import { trackMCP } from "../lib/instrumentation.js";
|
|
5
6
|
import { parseAccessibilityReportFromCSV } from "./accessiblity-utils/report-parser.js";
|
|
6
7
|
import { queryAccessibilityRAG } from "./accessiblity-utils/accessibility-rag.js";
|
|
7
8
|
import { getBrowserStackAuth } from "../lib/get-auth.js";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const scanner = new AccessibilityScanner();
|
|
9
|
+
import logger from "../logger.js";
|
|
10
|
+
function setupAuth(config) {
|
|
11
11
|
const authString = getBrowserStackAuth(config);
|
|
12
12
|
const [username, password] = authString.split(":");
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
13
|
+
return { username, password };
|
|
14
|
+
}
|
|
15
|
+
function createErrorResponse(message, isError = true) {
|
|
16
|
+
return {
|
|
17
|
+
content: [
|
|
18
|
+
{
|
|
19
|
+
type: "text",
|
|
20
|
+
text: message,
|
|
21
|
+
isError,
|
|
22
|
+
},
|
|
23
|
+
],
|
|
24
|
+
isError,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function createSuccessResponse(messages) {
|
|
28
|
+
return {
|
|
29
|
+
content: messages.map((text) => ({
|
|
30
|
+
type: "text",
|
|
31
|
+
text,
|
|
32
|
+
})),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function handleMCPError(toolName, server, config, error) {
|
|
36
|
+
trackMCP(toolName, server.server.getClientVersion(), error, config);
|
|
37
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
38
|
+
return createErrorResponse(`Failed to ${toolName.replace(/([A-Z])/g, " $1").toLowerCase()}: ${errorMessage}. Please open an issue on GitHub if the problem persists`);
|
|
39
|
+
}
|
|
40
|
+
async function notifyScanProgress(context, message, progress = 0) {
|
|
19
41
|
await context.sendNotification({
|
|
20
42
|
method: "notifications/progress",
|
|
21
43
|
params: {
|
|
22
|
-
progressToken: context._meta?.progressToken ?? "NOT_FOUND",
|
|
23
|
-
message
|
|
24
|
-
progress
|
|
44
|
+
progressToken: context._meta?.progressToken?.toString() ?? "NOT_FOUND",
|
|
45
|
+
message,
|
|
46
|
+
progress,
|
|
25
47
|
total: 100,
|
|
26
48
|
},
|
|
27
49
|
});
|
|
28
|
-
|
|
50
|
+
}
|
|
51
|
+
async function initializeScanner(config) {
|
|
52
|
+
const scanner = new AccessibilityScanner();
|
|
53
|
+
const auth = setupAuth(config);
|
|
54
|
+
scanner.setAuth(auth);
|
|
55
|
+
return scanner;
|
|
56
|
+
}
|
|
57
|
+
async function initializeReportFetcher(config) {
|
|
58
|
+
const reportFetcher = new AccessibilityReportFetcher();
|
|
59
|
+
const auth = setupAuth(config);
|
|
60
|
+
reportFetcher.setAuth(auth);
|
|
61
|
+
return reportFetcher;
|
|
62
|
+
}
|
|
63
|
+
async function executeAccessibilityRAG(args, server, config) {
|
|
64
|
+
try {
|
|
65
|
+
trackMCP("accessibilityExpert", server.server.getClientVersion(), undefined, config);
|
|
66
|
+
return await queryAccessibilityRAG(args.query, config);
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
return handleMCPError("accessibilityExpert", server, config, error);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async function executeFetchAccessibilityIssues(args, server, config) {
|
|
73
|
+
try {
|
|
74
|
+
trackMCP("fetchAccessibilityIssues", server.server.getClientVersion(), undefined, config);
|
|
75
|
+
return await fetchAccessibilityIssues(args.scanId, args.scanRunId, config, args.cursor);
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
return handleMCPError("fetchAccessibilityIssues", server, config, error);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async function fetchAccessibilityIssues(scanId, scanRunId, config, cursor = 0) {
|
|
82
|
+
const reportFetcher = await initializeReportFetcher(config);
|
|
83
|
+
const reportLink = await reportFetcher.getReportLink(scanId, scanRunId);
|
|
84
|
+
const { records, page_length, total_issues, next_page } = await parseAccessibilityReportFromCSV(reportLink, { nextPage: cursor });
|
|
85
|
+
const currentlyShown = cursor === 0
|
|
86
|
+
? page_length
|
|
87
|
+
: Math.floor(cursor / JSON.stringify(records[0] || {}).length) +
|
|
88
|
+
page_length;
|
|
89
|
+
const remainingIssues = total_issues - currentlyShown;
|
|
90
|
+
const messages = [
|
|
91
|
+
`Retrieved ${page_length} accessibility issues (Total: ${total_issues})`,
|
|
92
|
+
`Issues: ${JSON.stringify(records, null, 2)}`,
|
|
93
|
+
];
|
|
94
|
+
if (next_page !== null) {
|
|
95
|
+
messages.push(`${remainingIssues} more issues available. Use fetchAccessibilityIssues with cursor: ${next_page} to get the next batch.`);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
messages.push(`✅ All issues retrieved.`);
|
|
99
|
+
}
|
|
100
|
+
return createSuccessResponse(messages);
|
|
101
|
+
}
|
|
102
|
+
async function executeAccessibilityScan(args, context, server, config) {
|
|
103
|
+
try {
|
|
104
|
+
trackMCP("startAccessibilityScan", server.server.getClientVersion(), undefined, config);
|
|
105
|
+
return await runAccessibilityScan(args.name, args.pageURL, context, config, args.authConfigId);
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
return handleMCPError("startAccessibilityScan", server, config, error);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function validateFormAuthArgs(args) {
|
|
112
|
+
return (args.type === "form" &&
|
|
113
|
+
"usernameSelector" in args &&
|
|
114
|
+
"passwordSelector" in args &&
|
|
115
|
+
"submitSelector" in args &&
|
|
116
|
+
!!args.usernameSelector &&
|
|
117
|
+
!!args.passwordSelector &&
|
|
118
|
+
!!args.submitSelector);
|
|
119
|
+
}
|
|
120
|
+
async function createAuthConfig(args, config) {
|
|
121
|
+
const authConfig = new AccessibilityAuthConfig();
|
|
122
|
+
const auth = setupAuth(config);
|
|
123
|
+
authConfig.setAuth(auth);
|
|
124
|
+
if (args.type === "form") {
|
|
125
|
+
if (!validateFormAuthArgs(args)) {
|
|
126
|
+
throw new Error("Form authentication requires usernameSelector, passwordSelector, and submitSelector");
|
|
127
|
+
}
|
|
128
|
+
return await authConfig.createFormAuthConfig(args.name, {
|
|
129
|
+
username: args.username,
|
|
130
|
+
usernameSelector: args.usernameSelector,
|
|
131
|
+
password: args.password,
|
|
132
|
+
passwordSelector: args.passwordSelector,
|
|
133
|
+
submitSelector: args.submitSelector,
|
|
134
|
+
url: args.url,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
return await authConfig.createBasicAuthConfig(args.name, {
|
|
139
|
+
url: args.url,
|
|
140
|
+
username: args.username,
|
|
141
|
+
password: args.password,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
async function executeCreateAuthConfig(args, server, config) {
|
|
146
|
+
try {
|
|
147
|
+
trackMCP("createAccessibilityAuthConfig", server.server.getClientVersion(), undefined, config);
|
|
148
|
+
logger.info(`Creating auth config: ${JSON.stringify(args)}`);
|
|
149
|
+
const result = await createAuthConfig(args, config);
|
|
150
|
+
return createSuccessResponse([
|
|
151
|
+
`✅ Auth config "${args.name}" created successfully with ID: ${result.data?.id}`,
|
|
152
|
+
`Auth config details: ${JSON.stringify(result.data, null, 2)}`,
|
|
153
|
+
]);
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
if (error instanceof Error &&
|
|
157
|
+
error.message.includes("Form authentication requires")) {
|
|
158
|
+
return createErrorResponse(error.message);
|
|
159
|
+
}
|
|
160
|
+
return handleMCPError("createAccessibilityAuthConfig", server, config, error);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
async function executeGetAuthConfig(args, server, config) {
|
|
164
|
+
try {
|
|
165
|
+
trackMCP("getAccessibilityAuthConfig", server.server.getClientVersion(), undefined, config);
|
|
166
|
+
const authConfig = new AccessibilityAuthConfig();
|
|
167
|
+
const auth = setupAuth(config);
|
|
168
|
+
authConfig.setAuth(auth);
|
|
169
|
+
const result = await authConfig.getAuthConfig(args.configId);
|
|
170
|
+
return createSuccessResponse([
|
|
171
|
+
`✅ Auth config retrieved successfully`,
|
|
172
|
+
`Auth config details: ${JSON.stringify(result.data, null, 2)}`,
|
|
173
|
+
]);
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
return handleMCPError("getAccessibilityAuthConfig", server, config, error);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
function createScanFailureResponse(name, status) {
|
|
180
|
+
return createErrorResponse(`❌ Accessibility scan "${name}" failed with status: ${status} , check the BrowserStack dashboard for more details [https://scanner.browserstack.com/site-scanner/scan-details/${name}].`);
|
|
181
|
+
}
|
|
182
|
+
function createScanSuccessResponse(name, totalIssues, pageLength, records, scanId, scanRunId, reportUrl, cursor) {
|
|
183
|
+
const messages = [
|
|
184
|
+
`Accessibility scan "${name}" completed. check the BrowserStack dashboard for more details [https://scanner.browserstack.com/site-scanner/scan-details/${name}].`,
|
|
185
|
+
`Scan ID: ${scanId} and Scan Run ID: ${scanRunId}`,
|
|
186
|
+
`You can also download the full report from the following link: ${reportUrl}`,
|
|
187
|
+
`We found ${totalIssues} issues. Below are the details of the ${pageLength} most critical issues.`,
|
|
188
|
+
`Scan results: ${JSON.stringify(records, null, 2)}`,
|
|
189
|
+
];
|
|
190
|
+
if (cursor !== null) {
|
|
191
|
+
messages.push(`More issues available. Use fetchAccessibilityIssues tool with scanId: "${scanId}", scanRunId: "${scanRunId}", and cursor: ${cursor} to get the next batch.`);
|
|
192
|
+
}
|
|
193
|
+
return createSuccessResponse(messages);
|
|
194
|
+
}
|
|
195
|
+
async function runAccessibilityScan(name, pageURL, context, config, authConfigId) {
|
|
196
|
+
const scanner = await initializeScanner(config);
|
|
197
|
+
const startResp = await scanner.startScan(name, [pageURL], authConfigId);
|
|
198
|
+
const scanId = startResp.data.id;
|
|
199
|
+
const scanRunId = startResp.data.scanRunId;
|
|
200
|
+
await notifyScanProgress(context, `Accessibility scan "${name}" started`, 0);
|
|
29
201
|
const status = await scanner.waitUntilComplete(scanId, scanRunId, context);
|
|
30
202
|
if (status !== "completed") {
|
|
31
|
-
return
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
type: "text",
|
|
35
|
-
text: `❌ Accessibility scan "${name}" failed with status: ${status} , check the BrowserStack dashboard for more details [https://scanner.browserstack.com/site-scanner/scan-details/${name}].`,
|
|
36
|
-
isError: true,
|
|
37
|
-
},
|
|
38
|
-
],
|
|
39
|
-
isError: true,
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
// Create report fetcher and set auth on the go
|
|
43
|
-
const reportFetcher = new AccessibilityReportFetcher();
|
|
44
|
-
reportFetcher.setAuth({ username, password });
|
|
45
|
-
// Fetch CSV report link
|
|
203
|
+
return createScanFailureResponse(name, status);
|
|
204
|
+
}
|
|
205
|
+
const reportFetcher = await initializeReportFetcher(config);
|
|
46
206
|
const reportLink = await reportFetcher.getReportLink(scanId, scanRunId);
|
|
47
|
-
const { records, page_length, total_issues } = await parseAccessibilityReportFromCSV(reportLink);
|
|
48
|
-
return
|
|
49
|
-
content: [
|
|
50
|
-
{
|
|
51
|
-
type: "text",
|
|
52
|
-
text: `✅ Accessibility scan "${name}" completed. check the BrowserStack dashboard for more details [https://scanner.browserstack.com/site-scanner/scan-details/${name}].`,
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
type: "text",
|
|
56
|
-
text: `We found ${total_issues} issues. Below are the details of the ${page_length} most critical issues.`,
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
type: "text",
|
|
60
|
-
text: `Scan results: ${JSON.stringify(records, null, 2)}`,
|
|
61
|
-
},
|
|
62
|
-
],
|
|
63
|
-
};
|
|
207
|
+
const { records, page_length, total_issues, next_page } = await parseAccessibilityReportFromCSV(reportLink);
|
|
208
|
+
return createScanSuccessResponse(name, total_issues, page_length, records, scanId, scanRunId, reportLink, next_page);
|
|
64
209
|
}
|
|
65
210
|
export default function addAccessibilityTools(server, config) {
|
|
66
211
|
const tools = {};
|
|
@@ -69,44 +214,59 @@ export default function addAccessibilityTools(server, config) {
|
|
|
69
214
|
.string()
|
|
70
215
|
.describe("Any accessibility, a11y, WCAG, or web accessibility question"),
|
|
71
216
|
}, async (args) => {
|
|
72
|
-
|
|
73
|
-
trackMCP("accessibilityExpert", server.server.getClientVersion(), undefined, config);
|
|
74
|
-
return await queryAccessibilityRAG(args.query, config);
|
|
75
|
-
}
|
|
76
|
-
catch (error) {
|
|
77
|
-
trackMCP("accessibilityExpert", server.server.getClientVersion(), error, config);
|
|
78
|
-
return {
|
|
79
|
-
content: [
|
|
80
|
-
{
|
|
81
|
-
type: "text",
|
|
82
|
-
text: `Failed to query accessibility RAG: ${error instanceof Error ? error.message : "Unknown error"}. Please open an issue on GitHub if the problem persists`,
|
|
83
|
-
},
|
|
84
|
-
],
|
|
85
|
-
isError: true,
|
|
86
|
-
};
|
|
87
|
-
}
|
|
217
|
+
return await executeAccessibilityRAG(args, server, config);
|
|
88
218
|
});
|
|
89
219
|
tools.startAccessibilityScan = server.tool("startAccessibilityScan", "Start an accessibility scan via BrowserStack and retrieve a local CSV report path.", {
|
|
90
220
|
name: z.string().describe("Name of the accessibility scan"),
|
|
91
221
|
pageURL: z.string().describe("The URL to scan for accessibility issues"),
|
|
222
|
+
authConfigId: z
|
|
223
|
+
.number()
|
|
224
|
+
.optional()
|
|
225
|
+
.describe("Optional auth config ID for authenticated scans"),
|
|
92
226
|
}, async (args, context) => {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
227
|
+
return await executeAccessibilityScan(args, context, server, config);
|
|
228
|
+
});
|
|
229
|
+
tools.createAccessibilityAuthConfig = server.tool("createAccessibilityAuthConfig", "Create an authentication configuration for accessibility scans. Supports both form-based and basic authentication.", {
|
|
230
|
+
name: z.string().describe("Name for the auth configuration"),
|
|
231
|
+
type: z
|
|
232
|
+
.enum(["form", "basic"])
|
|
233
|
+
.describe("Authentication type: 'form' for form-based auth, 'basic' for HTTP basic auth"),
|
|
234
|
+
url: z.string().describe("URL of the authentication page"),
|
|
235
|
+
username: z.string().describe("Username for authentication"),
|
|
236
|
+
password: z.string().describe("Password for authentication"),
|
|
237
|
+
usernameSelector: z
|
|
238
|
+
.string()
|
|
239
|
+
.optional()
|
|
240
|
+
.describe("CSS selector for username field (required for form auth)"),
|
|
241
|
+
passwordSelector: z
|
|
242
|
+
.string()
|
|
243
|
+
.optional()
|
|
244
|
+
.describe("CSS selector for password field (required for form auth)"),
|
|
245
|
+
submitSelector: z
|
|
246
|
+
.string()
|
|
247
|
+
.optional()
|
|
248
|
+
.describe("CSS selector for submit button (required for form auth)"),
|
|
249
|
+
}, async (args) => {
|
|
250
|
+
return await executeCreateAuthConfig(args, server, config);
|
|
251
|
+
});
|
|
252
|
+
tools.getAccessibilityAuthConfig = server.tool("getAccessibilityAuthConfig", "Retrieve an existing authentication configuration by ID.", {
|
|
253
|
+
configId: z.number().describe("ID of the auth configuration to retrieve"),
|
|
254
|
+
}, async (args) => {
|
|
255
|
+
return await executeGetAuthConfig(args, server, config);
|
|
256
|
+
});
|
|
257
|
+
tools.fetchAccessibilityIssues = server.tool("fetchAccessibilityIssues", "Fetch accessibility issues from a completed scan with pagination support. Use cursor parameter to get subsequent pages of results.", {
|
|
258
|
+
scanId: z
|
|
259
|
+
.string()
|
|
260
|
+
.describe("The scan ID from a completed accessibility scan"),
|
|
261
|
+
scanRunId: z
|
|
262
|
+
.string()
|
|
263
|
+
.describe("The scan run ID from a completed accessibility scan"),
|
|
264
|
+
cursor: z
|
|
265
|
+
.number()
|
|
266
|
+
.optional()
|
|
267
|
+
.describe("Character offset for pagination (default: 0)"),
|
|
268
|
+
}, async (args) => {
|
|
269
|
+
return await executeFetchAccessibilityIssues(args, server, config);
|
|
110
270
|
});
|
|
111
271
|
return tools;
|
|
112
272
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export interface AuthConfigResponse {
|
|
2
|
+
success: boolean;
|
|
3
|
+
data?: {
|
|
4
|
+
id: number;
|
|
5
|
+
name: string;
|
|
6
|
+
type: string;
|
|
7
|
+
username?: string;
|
|
8
|
+
password?: string;
|
|
9
|
+
url?: string;
|
|
10
|
+
usernameSelector?: string;
|
|
11
|
+
passwordSelector?: string;
|
|
12
|
+
submitSelector?: string;
|
|
13
|
+
};
|
|
14
|
+
errors?: string[];
|
|
15
|
+
}
|
|
16
|
+
export interface FormAuthData {
|
|
17
|
+
username: string;
|
|
18
|
+
usernameSelector: string;
|
|
19
|
+
password: string;
|
|
20
|
+
passwordSelector: string;
|
|
21
|
+
submitSelector: string;
|
|
22
|
+
url: string;
|
|
23
|
+
}
|
|
24
|
+
export interface BasicAuthData {
|
|
25
|
+
url: string;
|
|
26
|
+
username: string;
|
|
27
|
+
password: string;
|
|
28
|
+
}
|
|
29
|
+
export declare class AccessibilityAuthConfig {
|
|
30
|
+
private auth;
|
|
31
|
+
setAuth(auth: {
|
|
32
|
+
username: string;
|
|
33
|
+
password: string;
|
|
34
|
+
}): void;
|
|
35
|
+
private transformLocalUrl;
|
|
36
|
+
createFormAuthConfig(name: string, authData: FormAuthData): Promise<AuthConfigResponse>;
|
|
37
|
+
createBasicAuthConfig(name: string, authData: BasicAuthData): Promise<AuthConfigResponse>;
|
|
38
|
+
getAuthConfig(configId: number): Promise<AuthConfigResponse>;
|
|
39
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { apiClient } from "../../lib/apiClient.js";
|
|
2
|
+
import logger from "../../logger.js";
|
|
3
|
+
export class AccessibilityAuthConfig {
|
|
4
|
+
auth;
|
|
5
|
+
setAuth(auth) {
|
|
6
|
+
this.auth = auth;
|
|
7
|
+
}
|
|
8
|
+
transformLocalUrl(url) {
|
|
9
|
+
try {
|
|
10
|
+
const parsed = new URL(url);
|
|
11
|
+
const localHosts = new Set(["127.0.0.1", "localhost", "0.0.0.0"]);
|
|
12
|
+
const BS_LOCAL_DOMAIN = "bs-local.com";
|
|
13
|
+
if (localHosts.has(parsed.hostname)) {
|
|
14
|
+
parsed.hostname = BS_LOCAL_DOMAIN;
|
|
15
|
+
return parsed.toString();
|
|
16
|
+
}
|
|
17
|
+
return url;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return url;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
async createFormAuthConfig(name, authData) {
|
|
24
|
+
if (!this.auth?.username || !this.auth?.password) {
|
|
25
|
+
throw new Error("BrowserStack credentials are not set for AccessibilityAuthConfig.");
|
|
26
|
+
}
|
|
27
|
+
const transformedAuthData = {
|
|
28
|
+
...authData,
|
|
29
|
+
url: this.transformLocalUrl(authData.url),
|
|
30
|
+
};
|
|
31
|
+
const requestBody = {
|
|
32
|
+
name,
|
|
33
|
+
type: "form",
|
|
34
|
+
authData: transformedAuthData,
|
|
35
|
+
};
|
|
36
|
+
try {
|
|
37
|
+
const response = await apiClient.post({
|
|
38
|
+
url: "https://api-accessibility.browserstack.com/api/website-scanner/v1/auth_configs",
|
|
39
|
+
headers: {
|
|
40
|
+
Authorization: "Basic " +
|
|
41
|
+
Buffer.from(`${this.auth.username}:${this.auth.password}`).toString("base64"),
|
|
42
|
+
"Content-Type": "application/json",
|
|
43
|
+
},
|
|
44
|
+
body: requestBody,
|
|
45
|
+
});
|
|
46
|
+
const data = response.data;
|
|
47
|
+
logger.info(`The data returned from the API is: ${JSON.stringify(data)}`);
|
|
48
|
+
if (!data.success) {
|
|
49
|
+
throw new Error(`Unable to create auth config: ${data.errors?.join(", ")}`);
|
|
50
|
+
}
|
|
51
|
+
return data;
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
logger.error(`Error creating form auth config: ${JSON.stringify(err?.response?.data)}`);
|
|
55
|
+
const msg = err?.response?.data?.error ||
|
|
56
|
+
err?.response?.data?.message ||
|
|
57
|
+
err?.message ||
|
|
58
|
+
String(err);
|
|
59
|
+
throw new Error(`Failed to create form auth config: ${msg}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
async createBasicAuthConfig(name, authData) {
|
|
63
|
+
if (!this.auth?.username || !this.auth?.password) {
|
|
64
|
+
throw new Error("BrowserStack credentials are not set for AccessibilityAuthConfig.");
|
|
65
|
+
}
|
|
66
|
+
const transformedAuthData = {
|
|
67
|
+
...authData,
|
|
68
|
+
url: this.transformLocalUrl(authData.url),
|
|
69
|
+
};
|
|
70
|
+
const requestBody = {
|
|
71
|
+
name,
|
|
72
|
+
type: "basic",
|
|
73
|
+
authData: transformedAuthData,
|
|
74
|
+
};
|
|
75
|
+
try {
|
|
76
|
+
const response = await apiClient.post({
|
|
77
|
+
url: "https://api-accessibility.browserstack.com/api/website-scanner/v1/auth_configs",
|
|
78
|
+
headers: {
|
|
79
|
+
Authorization: "Basic " +
|
|
80
|
+
Buffer.from(`${this.auth.username}:${this.auth.password}`).toString("base64"),
|
|
81
|
+
"Content-Type": "application/json",
|
|
82
|
+
},
|
|
83
|
+
body: requestBody,
|
|
84
|
+
});
|
|
85
|
+
const data = response.data;
|
|
86
|
+
if (!data.success) {
|
|
87
|
+
throw new Error(`Unable to create auth config: ${data.errors?.join(", ")}`);
|
|
88
|
+
}
|
|
89
|
+
return data;
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
const msg = err?.response?.data?.error ||
|
|
93
|
+
err?.response?.data?.message ||
|
|
94
|
+
err?.message ||
|
|
95
|
+
String(err);
|
|
96
|
+
throw new Error(`Failed to create basic auth config: ${msg}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
async getAuthConfig(configId) {
|
|
100
|
+
if (!this.auth?.username || !this.auth?.password) {
|
|
101
|
+
throw new Error("BrowserStack credentials are not set for AccessibilityAuthConfig.");
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
const response = await apiClient.get({
|
|
105
|
+
url: `https://api-accessibility.browserstack.com/api/website-scanner/v1/auth_configs/${configId}`,
|
|
106
|
+
headers: {
|
|
107
|
+
Authorization: "Basic " +
|
|
108
|
+
Buffer.from(`${this.auth.username}:${this.auth.password}`).toString("base64"),
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
const data = response.data;
|
|
112
|
+
if (!data.success) {
|
|
113
|
+
throw new Error(`Unable to get auth config: ${data.errors?.join(", ")}`);
|
|
114
|
+
}
|
|
115
|
+
return data;
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
const msg = err?.response?.data?.error ||
|
|
119
|
+
err?.response?.data?.message ||
|
|
120
|
+
err?.message ||
|
|
121
|
+
String(err);
|
|
122
|
+
throw new Error(`Failed to get auth config: ${msg}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -19,7 +19,7 @@ export declare class AccessibilityScanner {
|
|
|
19
19
|
username: string;
|
|
20
20
|
password: string;
|
|
21
21
|
}): void;
|
|
22
|
-
startScan(name: string, urlList: string[]): Promise<AccessibilityScanResponse>;
|
|
22
|
+
startScan(name: string, urlList: string[], authConfigId?: number): Promise<AccessibilityScanResponse>;
|
|
23
23
|
pollStatus(scanId: string, scanRunId: string): Promise<AccessibilityScanStatus>;
|
|
24
24
|
waitUntilComplete(scanId: string, scanRunId: string, context: any): Promise<string>;
|
|
25
25
|
}
|
|
@@ -8,7 +8,7 @@ export class AccessibilityScanner {
|
|
|
8
8
|
setAuth(auth) {
|
|
9
9
|
this.auth = auth;
|
|
10
10
|
}
|
|
11
|
-
async startScan(name, urlList) {
|
|
11
|
+
async startScan(name, urlList, authConfigId) {
|
|
12
12
|
if (!this.auth?.username || !this.auth?.password) {
|
|
13
13
|
throw new Error("BrowserStack credentials are not set for AccessibilityScanner.");
|
|
14
14
|
}
|
|
@@ -47,6 +47,7 @@ export class AccessibilityScanner {
|
|
|
47
47
|
name,
|
|
48
48
|
urlList: transformedUrlList,
|
|
49
49
|
recurring: false,
|
|
50
|
+
...(authConfigId && { authConfigId }),
|
|
50
51
|
};
|
|
51
52
|
let requestBody = baseRequestBody;
|
|
52
53
|
if (hasLocal) {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { testFilePathsMap } from "../lib/inmemory-store.js";
|
|
2
|
+
import { updateFileAndStep } from "./percy-snapshot-utils/utils.js";
|
|
3
|
+
import { percyWebSetupInstructions } from "../tools/sdk-utils/percy-web/handler.js";
|
|
4
|
+
export async function updateTestsWithPercyCommands(args) {
|
|
5
|
+
const { uuid, index } = args;
|
|
6
|
+
const filePaths = testFilePathsMap.get(uuid);
|
|
7
|
+
if (!filePaths) {
|
|
8
|
+
throw new Error(`No test files found in memory for UUID: ${uuid}`);
|
|
9
|
+
}
|
|
10
|
+
if (index < 0 || index >= filePaths.length) {
|
|
11
|
+
throw new Error(`Invalid index: ${index}. There are ${filePaths.length} files for UUID: ${uuid}`);
|
|
12
|
+
}
|
|
13
|
+
const result = await updateFileAndStep(filePaths[index], index, filePaths.length, percyWebSetupInstructions);
|
|
14
|
+
return {
|
|
15
|
+
content: result,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
@@ -1,17 +1,4 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
-
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
-
import { SDKSupportedBrowserAutomationFramework, SDKSupportedLanguage, SDKSupportedTestingFramework } from "./sdk-utils/types.js";
|
|
4
2
|
import { BrowserStackConfig } from "../lib/types.js";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
* This tool gives instructions to setup a browserstack.yml file in the project root and installs the necessary dependencies.
|
|
8
|
-
*/
|
|
9
|
-
export declare function bootstrapProjectWithSDK({ detectedBrowserAutomationFramework, detectedTestingFramework, detectedLanguage, desiredPlatforms, enablePercy, config, }: {
|
|
10
|
-
detectedBrowserAutomationFramework: SDKSupportedBrowserAutomationFramework;
|
|
11
|
-
detectedTestingFramework: SDKSupportedTestingFramework;
|
|
12
|
-
detectedLanguage: SDKSupportedLanguage;
|
|
13
|
-
desiredPlatforms: string[];
|
|
14
|
-
enablePercy: boolean;
|
|
15
|
-
config: BrowserStackConfig;
|
|
16
|
-
}): Promise<CallToolResult>;
|
|
17
|
-
export default function addSDKTools(server: McpServer, config: BrowserStackConfig): Record<string, any>;
|
|
3
|
+
export declare function registerRunBrowserStackTestsTool(server: McpServer, config: BrowserStackConfig): Record<string, any>;
|
|
4
|
+
export default registerRunBrowserStackTestsTool;
|