@browserstack/mcp-server 1.2.2 → 1.2.3-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/README.md +225 -0
- package/dist/lib/instrumentation.js +2 -0
- package/dist/server-factory.js +1 -1
- 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/appautomate-utils/appium-sdk/config-generator.d.ts +1 -0
- package/dist/tools/appautomate-utils/appium-sdk/config-generator.js +50 -0
- package/dist/tools/appautomate-utils/appium-sdk/constants.d.ts +23 -0
- package/dist/tools/appautomate-utils/appium-sdk/constants.js +43 -0
- package/dist/tools/appautomate-utils/appium-sdk/formatter.d.ts +8 -0
- package/dist/tools/appautomate-utils/appium-sdk/formatter.js +59 -0
- package/dist/tools/appautomate-utils/appium-sdk/handler.d.ts +3 -0
- package/dist/tools/appautomate-utils/appium-sdk/handler.js +52 -0
- package/dist/tools/appautomate-utils/appium-sdk/index.d.ts +7 -0
- package/dist/tools/appautomate-utils/appium-sdk/index.js +8 -0
- package/dist/tools/appautomate-utils/appium-sdk/instructions.d.ts +3 -0
- package/dist/tools/appautomate-utils/appium-sdk/instructions.js +47 -0
- package/dist/tools/appautomate-utils/appium-sdk/languages/csharp.d.ts +2 -0
- package/dist/tools/appautomate-utils/appium-sdk/languages/csharp.js +78 -0
- package/dist/tools/appautomate-utils/appium-sdk/languages/java.d.ts +8 -0
- package/dist/tools/appautomate-utils/appium-sdk/languages/java.js +87 -0
- package/dist/tools/appautomate-utils/appium-sdk/languages/nodejs.d.ts +3 -0
- package/dist/tools/appautomate-utils/appium-sdk/languages/nodejs.js +194 -0
- package/dist/tools/appautomate-utils/appium-sdk/languages/python.d.ts +3 -0
- package/dist/tools/appautomate-utils/appium-sdk/languages/python.js +76 -0
- package/dist/tools/appautomate-utils/appium-sdk/languages/ruby.d.ts +2 -0
- package/dist/tools/appautomate-utils/appium-sdk/languages/ruby.js +85 -0
- package/dist/tools/appautomate-utils/appium-sdk/types.d.ts +57 -0
- package/dist/tools/appautomate-utils/appium-sdk/types.js +61 -0
- package/dist/tools/appautomate-utils/appium-sdk/utils.d.ts +17 -0
- package/dist/tools/appautomate-utils/appium-sdk/utils.js +61 -0
- package/dist/tools/appautomate-utils/{appautomate.d.ts → native-execution/appautomate.d.ts} +1 -1
- package/dist/tools/appautomate-utils/{appautomate.js → native-execution/appautomate.js} +2 -2
- package/dist/tools/appautomate-utils/native-execution/constants.d.ts +10 -0
- package/dist/tools/appautomate-utils/native-execution/constants.js +36 -0
- package/dist/tools/appautomate-utils/native-execution/types.d.ts +19 -0
- package/dist/tools/appautomate-utils/{types.js → native-execution/types.js} +5 -1
- package/dist/tools/appautomate.js +25 -40
- package/dist/tools/sdk-utils/constants.js +10 -0
- package/package.json +1 -1
- package/dist/tools/appautomate-utils/types.d.ts +0 -5
- /package/dist/tools/{getFailureLogs.d.ts → get-failure-logs.d.ts} +0 -0
- /package/dist/tools/{getFailureLogs.js → get-failure-logs.js} +0 -0
package/README.md
CHANGED
|
@@ -35,6 +35,10 @@ Manage, execute, debug tests, and even fix code using plain English prompts.
|
|
|
35
35
|
#### Reduced context switching:
|
|
36
36
|
Stay in flow—keep all project context in one place and trigger actions directly from your IDE or LLM.
|
|
37
37
|
|
|
38
|
+
## ⚡️ One Click MCP Setup
|
|
39
|
+
|
|
40
|
+
[](http://mcp.browserstack.com/one-click-setup?client=vscode) [](http://mcp.browserstack.com/one-click-setup?client=cursor)
|
|
41
|
+
|
|
38
42
|
## 💡 Usage Examples
|
|
39
43
|
|
|
40
44
|
### 📱 Manual App Testing
|
|
@@ -138,8 +142,13 @@ Generate test cases from PRDs, convert manual tests to low-code automation, and
|
|
|
138
142
|
|
|
139
143
|
## 🛠️ Installation
|
|
140
144
|
|
|
145
|
+
### **One Click MCP Setup**
|
|
146
|
+
|
|
141
147
|
[](http://mcp.browserstack.com/one-click-setup?client=vscode) [](http://mcp.browserstack.com/one-click-setup?client=cursor)
|
|
142
148
|
|
|
149
|
+
|
|
150
|
+
### **Alternate ways to Setup MCP server**
|
|
151
|
+
|
|
143
152
|
1. **Create a BrowserStack Account**
|
|
144
153
|
|
|
145
154
|
- Sign up for [BrowserStack](https://www.browserstack.com/users/sign_up) if you don't have an account already.
|
|
@@ -150,9 +159,21 @@ Generate test cases from PRDs, convert manual tests to low-code automation, and
|
|
|
150
159
|
- Once you have an account (and purchased appropriate plan), note down your `username` and `access_key` from [Account Settings](https://www.browserstack.com/accounts/profile/details).
|
|
151
160
|
|
|
152
161
|
2. Ensure you are using Node version >= `18.0`. Check your node version using `node --version`. Recommended version: `v22.15.0` (LTS)
|
|
162
|
+
|
|
153
163
|
3. **Install the MCP Server**
|
|
154
164
|
|
|
155
165
|
- VSCode (Copilot - Agent Mode): `.vscode/mcp.json`:
|
|
166
|
+
|
|
167
|
+
- Locate or Create the Configuration File:
|
|
168
|
+
In the root directory of your project, look for a folder named .vscode. This folder is usually hidden so you will need to find it as mentioned in the expand.
|
|
169
|
+
|
|
170
|
+
- If this folder doesn't exist, create it.
|
|
171
|
+
|
|
172
|
+
- Inside the .vscode folder, create a new file named mcp.json
|
|
173
|
+
|
|
174
|
+
- Add the Configuration: Open the mcp.json file and then add the following JSON content.
|
|
175
|
+
|
|
176
|
+
- Replace the username and <access_key> with your BrowserStack credentials.
|
|
156
177
|
|
|
157
178
|
```json
|
|
158
179
|
{
|
|
@@ -172,6 +193,30 @@ Generate test cases from PRDs, convert manual tests to low-code automation, and
|
|
|
172
193
|
- In VSCode, make sure to click on `Start` button in the MCP Server to start the server.
|
|
173
194
|

|
|
174
195
|
|
|
196
|
+
|
|
197
|
+
#### ** Alternate way to setup MCP on VSCode Copilot
|
|
198
|
+
|
|
199
|
+
1.Click on the gear icon to Select Tools
|
|
200
|
+
<div align="center">
|
|
201
|
+
<img src="assets/select_tools.png" alt="Select Tools" height="100">
|
|
202
|
+
</div>
|
|
203
|
+
2. A tool menu would appear at the top-centre, scroll down on the menu at the top and then Click on Add MCP Server
|
|
204
|
+
<div align="center">
|
|
205
|
+
<img src="assets/add_mcp_server.png" alt="Add MCP Server" height="100">
|
|
206
|
+
</div>
|
|
207
|
+
3. Select NPM package option (Install fron an NPM package) - 3rd in the list
|
|
208
|
+
<div align="center">
|
|
209
|
+
<img src="assets/select_npm_package.png" alt="Select NPM Package" height="100">
|
|
210
|
+
</div>
|
|
211
|
+
4. Enter NPM Package Name (@browserstack/mcp-server)
|
|
212
|
+
<div align="center">
|
|
213
|
+
<img src="assets/enter_npm_package.png" alt="Enter NPM Package" height="100">
|
|
214
|
+
</div>
|
|
215
|
+
5. Enter browserstack user name and access key
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
|
|
175
220
|
* For Cursor: `.cursor/mcp.json`:
|
|
176
221
|
|
|
177
222
|
```json
|
|
@@ -233,6 +278,186 @@ To install BrowserStack Test Platform Server for Claude Desktop automatically vi
|
|
|
233
278
|
npx -y @smithery/cli install @browserstack/mcp-server --client claude
|
|
234
279
|
```
|
|
235
280
|
|
|
281
|
+
|
|
282
|
+
### 💡 List of BrowserStack MCP Tools
|
|
283
|
+
|
|
284
|
+
As of now we support 20 tools.
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
## 🧾 Test Management
|
|
290
|
+
|
|
291
|
+
1. `createProjectOrFolder` — Create a Test Management project and/or folders to organize test cases. Returns with Folder ID, Project ID and Test Management Link to access the TM Project Dashboard.
|
|
292
|
+
**Prompt example**
|
|
293
|
+
|
|
294
|
+
```text
|
|
295
|
+
Create a new Test Management project named 'Shopping App' with two folders - Login and Checkout
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
2. `createTestCase` — Add a manual test case under a specific project/folder (uses project identifier like PR-xxxxx and a folder ID).
|
|
300
|
+
**Prompt example**
|
|
301
|
+
|
|
302
|
+
```text
|
|
303
|
+
Add a test case named 'Invalid Login Scenario' to the Login folder in the 'Shopping App' project with PR-53617, Folder ID: 117869
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
3. `listTestCases` — List test cases for a project (supports filters like priority, status, tags).
|
|
307
|
+
**Prompt example**
|
|
308
|
+
|
|
309
|
+
```text
|
|
310
|
+
List all high-priority test cases in the 'Shopping App' project with project_identifier: PR-59457
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
4. `createTestRun` — Create a test run (suite) for selected test cases in a project.
|
|
314
|
+
**Prompt example**
|
|
315
|
+
|
|
316
|
+
```text
|
|
317
|
+
Create a test run for the Login folder in the 'Shopping App' project and name it 'Release v1.0 Login Flow'
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
5. `listTestRuns` — List test runs for a project (filter by dates, assignee, state).
|
|
321
|
+
**Prompt example**
|
|
322
|
+
|
|
323
|
+
```text
|
|
324
|
+
List all test runs from the 'Shopping App' project that were executed last week and are currently marked in-progress
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
6. `updateTestRun` — Partially update a test run (status, tags, notes, associated test cases).
|
|
328
|
+
**Prompt example**
|
|
329
|
+
|
|
330
|
+
```text
|
|
331
|
+
Update test run ID 1043 in the 'Shopping App' project and mark it as complete with the note 'Regression cycle done'
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
7. `addTestResult` — Add a manual execution result (passed/failed/blocked/skipped) for a test case within a run.
|
|
335
|
+
**Prompt example**
|
|
336
|
+
|
|
337
|
+
```text
|
|
338
|
+
Mark the test case 'Invalid Login Scenario' as passed in test run ID 1043 of the 'Shopping App' project
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
8. `createTestCasesFromFile` — Bulk-create test cases from an uploaded file (e.g., PDF).
|
|
342
|
+
**Prompt example**
|
|
343
|
+
|
|
344
|
+
```text
|
|
345
|
+
Upload test cases from '/Users/xyz/testcases.pdf' to the 'Shopping App' project in Test Management
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
---
|
|
349
|
+
|
|
350
|
+
## ⚙️ BrowserStack SDK Setup / Automate Test
|
|
351
|
+
|
|
352
|
+
9. `setupBrowserStackAutomateTests` — Integrate BrowserStack SDK and run web tests on BrowserStack (optionally enable Percy).
|
|
353
|
+
**Prompt example**
|
|
354
|
+
|
|
355
|
+
```text
|
|
356
|
+
Run my Selenium-JUnit5 tests written in Java on Chrome and Firefox. Enable Percy for visual testing.
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
10. `fetchAutomationScreenshots` — Fetch screenshots captured during a given Automate/App Automate session.
|
|
360
|
+
**Prompt example**
|
|
361
|
+
|
|
362
|
+
```text
|
|
363
|
+
Get screenshots from Automate session ID abc123xyz for my desktop test run
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
---
|
|
367
|
+
|
|
368
|
+
## 🔍 Observability
|
|
369
|
+
|
|
370
|
+
11. `getFailureLogs` — Retrieve error logs for Automate/App Automate sessions (optionally by Build ID for App Automate).
|
|
371
|
+
**Prompt example**
|
|
372
|
+
|
|
373
|
+
```text
|
|
374
|
+
Get the error logs from the session ID: 21a864032a7459f1e7634222249b316759d6827f, Build ID: dt7ung4wmjittzff8kksrjadjax9gzvbscoyf9qn of App Automate test session
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
---
|
|
378
|
+
|
|
379
|
+
## 📱 App Live
|
|
380
|
+
|
|
381
|
+
12. `runAppLiveSession` — Start a manual app testing session on a real device in the cloud.
|
|
382
|
+
**Prompt example**
|
|
383
|
+
|
|
384
|
+
```text
|
|
385
|
+
Open my app on iPhone 15 Pro Max with iOS 17. App path is /Users/xyz/app.ipa
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
---
|
|
389
|
+
|
|
390
|
+
## 💻 Live
|
|
391
|
+
|
|
392
|
+
13. `runBrowserLiveSession` — Start a Live session for website testing on desktop or mobile browsers.
|
|
393
|
+
**Prompt example**
|
|
394
|
+
|
|
395
|
+
```text
|
|
396
|
+
Open www.google.com on the latest version of Microsoft Edge on Windows 11
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
---
|
|
400
|
+
|
|
401
|
+
## 📲 App Automate
|
|
402
|
+
|
|
403
|
+
14. `takeAppScreenshot` — Launch the app on a specified device and captures a quick verification screenshot. This tool is just to verify whether your app has been launched.
|
|
404
|
+
**Prompt example**
|
|
405
|
+
|
|
406
|
+
```text
|
|
407
|
+
Take a screenshot of my app on Google Pixel 6 with Android 14 while testing on App Automate. App file path: /Users/xyz/app-debug.apk
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
15. `runAppTestsOnBrowserStack` — Run automated mobile tests (Espresso/XCUITest, etc.) on real devices.
|
|
411
|
+
**Prompt example**
|
|
412
|
+
|
|
413
|
+
```text
|
|
414
|
+
Run Espresso tests from /tests/checkout.zip on Galaxy S21 and Pixel 6 with Android 14. App path is /apps/beta-release.apk under project 'Checkout Flow'
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
---
|
|
418
|
+
|
|
419
|
+
## ♿ Accessibility
|
|
420
|
+
|
|
421
|
+
16. `accessibilityExpert` — Ask A11y Expert (WCAG 2.0/2.1/2.2, mobile/web usability, best practices).
|
|
422
|
+
**Prompt example**
|
|
423
|
+
|
|
424
|
+
```text
|
|
425
|
+
What WCAG guidelines apply to form field error messages on mobile web?
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
17. `startAccessibilityScan` — Start a web accessibility scan and return the result link.
|
|
429
|
+
**Prompt example**
|
|
430
|
+
|
|
431
|
+
```text
|
|
432
|
+
Run accessibility scan for "www.example.com"
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
---
|
|
436
|
+
|
|
437
|
+
## 🤖 BrowserStack AI Agents
|
|
438
|
+
|
|
439
|
+
18. `fetchSelfHealedSelectors` — Retrieve AI self-healed selectors to fix flaky tests due to DOM changes.
|
|
440
|
+
**Prompt example**
|
|
441
|
+
|
|
442
|
+
```text
|
|
443
|
+
Fetch and fix flaky test selectors in Automate session ID session_9482 using MCP
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
19. `createLCASteps` — Generate Low Code Automation steps from a manual test case in Test Management.
|
|
447
|
+
**Prompt example**
|
|
448
|
+
|
|
449
|
+
```text
|
|
450
|
+
Convert the manual test case 'Add to Cart' in the 'Shopping App' project into LCA steps
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
20. `uploadProductRequirementFile` — Upload a PRD/screenshot/PDF and get a file mapping ID (used with `createTestCasesFromFile`).
|
|
454
|
+
**Prompt example**
|
|
455
|
+
|
|
456
|
+
```text
|
|
457
|
+
Upload PRD from /Users/xyz/Desktop/login-flow.pdf and use BrowserStack AI to generate test cases
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
|
|
236
461
|
## 🤝 Recommended MCP Clients
|
|
237
462
|
|
|
238
463
|
- We recommend using **Github Copilot or Cursor** for automated testing + debugging use cases.
|
|
@@ -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
|
@@ -8,7 +8,7 @@ import addBrowserLiveTools from "./tools/live.js";
|
|
|
8
8
|
import addAccessibilityTools from "./tools/accessibility.js";
|
|
9
9
|
import addTestManagementTools from "./tools/testmanagement.js";
|
|
10
10
|
import addAppAutomationTools from "./tools/appautomate.js";
|
|
11
|
-
import addFailureLogsTools from "./tools/
|
|
11
|
+
import addFailureLogsTools from "./tools/get-failure-logs.js";
|
|
12
12
|
import addAutomateTools from "./tools/automate.js";
|
|
13
13
|
import addSelfHealTools from "./tools/selfheal.js";
|
|
14
14
|
import addAppLiveTools from "./tools/applive.js";
|
|
@@ -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
|
+
}
|