@browserstack/mcp-server 1.5.0-beta.2 → 1.5.0-beta.21
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 +48 -32
- package/dist/config.d.ts +4 -1
- package/dist/config.js +23 -2
- package/dist/index.d.ts +2 -1
- package/dist/index.js +7 -3
- package/dist/lib/constants.d.ts +3 -0
- package/dist/lib/constants.js +3 -0
- package/dist/lib/instrumentation.d.ts +14 -2
- package/dist/lib/instrumentation.js +104 -29
- package/dist/lib/node-version-notice.d.ts +1 -0
- package/dist/lib/node-version-notice.js +9 -0
- package/dist/lib/tool-latency.d.ts +4 -0
- package/dist/lib/tool-latency.js +16 -0
- package/dist/lib/untrusted-content.d.ts +15 -0
- package/dist/lib/untrusted-content.js +24 -0
- package/dist/lib/upload-validator.d.ts +3 -2
- package/dist/lib/upload-validator.js +18 -15
- package/dist/lib/utils.js +10 -3
- package/dist/lib/version-resolver.d.ts +0 -5
- package/dist/lib/version-resolver.js +8 -2
- package/dist/logger.js +1 -10
- package/dist/server-factory.js +10 -6
- package/dist/tools/accessibility.js +9 -5
- package/dist/tools/accessiblity-utils/accessibility-rag.js +3 -1
- package/dist/tools/appautomate-utils/native-execution/constants.js +4 -2
- package/dist/tools/appautomate.js +3 -1
- package/dist/tools/applive.js +1 -1
- package/dist/tools/ask-browserstack/central-oauth.d.ts +120 -0
- package/dist/tools/ask-browserstack/central-oauth.js +277 -0
- package/dist/tools/ask-browserstack/config.d.ts +102 -0
- package/dist/tools/ask-browserstack/config.js +140 -0
- package/dist/tools/ask-browserstack/egress.d.ts +34 -0
- package/dist/tools/ask-browserstack/egress.js +31 -0
- package/dist/tools/ask-browserstack/register.d.ts +61 -0
- package/dist/tools/ask-browserstack/register.js +416 -0
- package/dist/tools/ask-browserstack/relay.d.ts +201 -0
- package/dist/tools/ask-browserstack/relay.js +577 -0
- package/dist/tools/ask-browserstack/stream.d.ts +116 -0
- package/dist/tools/ask-browserstack/stream.js +236 -0
- package/dist/tools/ask-browserstack/types.d.ts +196 -0
- package/dist/tools/ask-browserstack/types.js +14 -0
- package/dist/tools/automate-utils/list-session-ids.d.ts +28 -0
- package/dist/tools/automate-utils/list-session-ids.js +87 -0
- package/dist/tools/automate-utils/resolve-hashed-build-id.d.ts +30 -0
- package/dist/tools/automate-utils/resolve-hashed-build-id.js +124 -0
- package/dist/tools/automate.d.ts +7 -0
- package/dist/tools/automate.js +104 -1
- package/dist/tools/bstack-sdk.js +2 -2
- package/dist/tools/build-insights.js +41 -2
- package/dist/tools/failurelogs-utils/app-automate.js +4 -3
- package/dist/tools/failurelogs-utils/automate.js +5 -4
- package/dist/tools/failurelogs-utils/resolve-app-build-id.d.ts +2 -0
- package/dist/tools/failurelogs-utils/resolve-app-build-id.js +5 -0
- package/dist/tools/failurelogs-utils/video.d.ts +3 -0
- package/dist/tools/failurelogs-utils/video.js +25 -0
- package/dist/tools/get-failure-logs.js +30 -11
- package/dist/tools/observability.js +3 -1
- package/dist/tools/percy-sdk.js +9 -9
- package/dist/tools/rca-agent-utils/format-rca.js +5 -4
- package/dist/tools/rca-agent-utils/get-failed-test-id.js +12 -0
- package/dist/tools/rca-agent-utils/types.d.ts +1 -0
- package/dist/tools/rca-agent.js +5 -2
- package/dist/tools/review-agent.js +2 -1
- package/dist/tools/sdk-utils/common/constants.d.ts +1 -1
- package/dist/tools/sdk-utils/common/constants.js +2 -1
- package/dist/tools/selfheal.js +6 -3
- package/dist/tools/testmanagement-utils/TCG-utils/api.js +26 -7
- package/dist/tools/testmanagement-utils/TCG-utils/config.d.ts +2 -0
- package/dist/tools/testmanagement-utils/TCG-utils/config.js +5 -0
- package/dist/tools/testmanagement-utils/testcase-from-file.js +2 -1
- package/dist/tools/testmanagement-utils/upload-file.js +16 -2
- package/dist/tools/testmanagement.js +39 -17
- package/dist/tools/tool-handoff.d.ts +66 -0
- package/dist/tools/tool-handoff.js +78 -0
- package/package.json +4 -5
- package/capability/loadtesting.capability-index.json +0 -1764
- package/capability/tm.capability-index.json +0 -19793
- package/dist/tools/capability-registry/bind.d.ts +0 -29
- package/dist/tools/capability-registry/bind.js +0 -134
- package/dist/tools/capability-registry/config.d.ts +0 -62
- package/dist/tools/capability-registry/config.js +0 -218
- package/dist/tools/capability-registry/discovery.d.ts +0 -44
- package/dist/tools/capability-registry/discovery.js +0 -99
- package/dist/tools/capability-registry/egress.d.ts +0 -44
- package/dist/tools/capability-registry/egress.js +0 -128
- package/dist/tools/capability-registry/index-loader.d.ts +0 -119
- package/dist/tools/capability-registry/index-loader.js +0 -314
- package/dist/tools/capability-registry/register.d.ts +0 -34
- package/dist/tools/capability-registry/register.js +0 -354
- package/dist/tools/capability-registry/resolve.d.ts +0 -38
- package/dist/tools/capability-registry/resolve.js +0 -45
- package/dist/tools/capability-registry/search.d.ts +0 -65
- package/dist/tools/capability-registry/search.js +0 -342
- package/dist/tools/capability-registry/types.d.ts +0 -208
- package/dist/tools/capability-registry/types.js +0 -33
package/README.md
CHANGED
|
@@ -43,7 +43,7 @@ Click on the buttons below to install MCP in your respective IDE:
|
|
|
43
43
|
|
|
44
44
|
> **Note:** The hosted Remote MCP server (`mcp.browserstack.com`) is now fully stateless over Streamable HTTP — any Streamable-HTTP client (Claude, Cursor, VS Code, ChatGPT) connects with no configuration change.
|
|
45
45
|
|
|
46
|
-
#### Note : Ensure you are using Node version >= `
|
|
46
|
+
#### Note : Ensure you are using Node version >= `20.9`
|
|
47
47
|
- Check your node version using `node --version`. Recommended version: `v22.15.0` (LTS)
|
|
48
48
|
- To Upgrade Node :
|
|
49
49
|
- 1. On macOS `(Homebrew) - brew update && brew upgrade node or if using (nvm) - nvm install 22.15.0 && nvm use 22.15.0 && nvm alias default 22.15.0`
|
|
@@ -158,7 +158,7 @@ Generate test cases from PRDs, convert manual tests to low-code automation, and
|
|
|
158
158
|
## 🛠️ Installation
|
|
159
159
|
|
|
160
160
|
### 📋 Prerequisites for MCP Setup
|
|
161
|
-
#### Note : Ensure you are using Node version >= `
|
|
161
|
+
#### Note : Ensure you are using Node version >= `20.9`
|
|
162
162
|
- Check your node version using `node --version`. Recommended version: `v22.15.0` (LTS)
|
|
163
163
|
|
|
164
164
|
### **One Click MCP Setup**
|
|
@@ -178,7 +178,7 @@ Click on the buttons below to install MCP in your respective IDE:
|
|
|
178
178
|
|
|
179
179
|
- 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).
|
|
180
180
|
|
|
181
|
-
2. #### Note : Ensure you are using Node version >= `
|
|
181
|
+
2. #### Note : Ensure you are using Node version >= `20.9`
|
|
182
182
|
- Check your node version using `node --version`. Recommended version: `v22.15.0` (LTS)
|
|
183
183
|
|
|
184
184
|
|
|
@@ -292,9 +292,11 @@ Select the “Installed” tab. Click the “Configure MCP Servers” button at
|
|
|
292
292
|
}
|
|
293
293
|
```
|
|
294
294
|
|
|
295
|
+
> **File & app uploads:** tools that upload a local file/app (`uploadProductRequirementFile`, `takeAppScreenshot`, `runAppTestsOnBrowserStack`, `runAppLiveSession`) require the `MCP_UPLOAD_BASE_DIR` env var set to a directory containing those files; uploads are restricted to it.
|
|
296
|
+
|
|
295
297
|
### 💡 List of BrowserStack MCP Tools
|
|
296
298
|
|
|
297
|
-
As of now we support
|
|
299
|
+
As of now we support 46 tools.
|
|
298
300
|
|
|
299
301
|
> **Remote MCP note:** Tools marked _(not available in Remote MCP)_ rely on local file/process state and are disabled in the multi-tenant [Remote MCP Server](#-remote-mcp-server). They are available in the local (npx) setup.
|
|
300
302
|
|
|
@@ -426,18 +428,25 @@ As of now we support 44 tools.
|
|
|
426
428
|
Get screenshots from Automate session ID abc123xyz for my desktop test run
|
|
427
429
|
```
|
|
428
430
|
|
|
431
|
+
18. `listSessions` — List the sessions in an Automate/App Automate build. Each record carries `sessionId`, `name`, `status`, `os`, `osVersion`, `browser`, `device`, `browserUrl` (dashboard link), and `videoUrl`, with optional `limit` / `offset` paging and a client-side `status` filter. Takes either the **hashed** build ID from the dashboard URL or the observability build id returned by `getBuildId` / `listBuildId` — an observability id is resolved to the hashed id automatically via the build's sessions. Returned `sessionId` values work with `getFailureLogs`, `fetchAutomationScreenshots`, and `fetchSelfHealedSelectors`.
|
|
432
|
+
**Prompt example**
|
|
433
|
+
|
|
434
|
+
```text
|
|
435
|
+
List sessions for Automate hashed build ID <hashed build id>
|
|
436
|
+
```
|
|
437
|
+
|
|
429
438
|
---
|
|
430
439
|
|
|
431
440
|
## 🔍 Observability
|
|
432
441
|
|
|
433
|
-
|
|
442
|
+
19. `getFailureLogs` — Retrieve error logs for Automate/App Automate sessions. App Automate log endpoints are build-scoped, so a hashed build ID is required there — pass one if you have it, otherwise it is resolved from the session automatically.
|
|
434
443
|
**Prompt example**
|
|
435
444
|
|
|
436
445
|
```text
|
|
437
|
-
Get the
|
|
446
|
+
Get the Appium logs for App Automate session ID <session id>
|
|
438
447
|
```
|
|
439
448
|
|
|
440
|
-
|
|
449
|
+
20. `fetchBuildInsights` — Fetch insights about a BrowserStack build by combining build details and quality-gate results. Includes `hashed_id` (the hashed build id `listSessions` takes) and `session_type`, resolved through the build's sessions when the build ran on Automate / App Automate.
|
|
441
450
|
**Prompt example**
|
|
442
451
|
|
|
443
452
|
```text
|
|
@@ -448,7 +457,7 @@ As of now we support 44 tools.
|
|
|
448
457
|
|
|
449
458
|
## 📱 App Live
|
|
450
459
|
|
|
451
|
-
|
|
460
|
+
21. `runAppLiveSession` — Start a manual app testing session on a real device in the cloud.
|
|
452
461
|
**Prompt example**
|
|
453
462
|
|
|
454
463
|
```text
|
|
@@ -459,7 +468,7 @@ As of now we support 44 tools.
|
|
|
459
468
|
|
|
460
469
|
## 💻 Live
|
|
461
470
|
|
|
462
|
-
|
|
471
|
+
22. `runBrowserLiveSession` — Start a Live session for website testing on desktop or mobile browsers.
|
|
463
472
|
**Prompt example**
|
|
464
473
|
|
|
465
474
|
```text
|
|
@@ -470,21 +479,21 @@ As of now we support 44 tools.
|
|
|
470
479
|
|
|
471
480
|
## 📲 App Automate
|
|
472
481
|
|
|
473
|
-
|
|
482
|
+
23. `takeAppScreenshot` — Launch the app on a specified device and capture a quick verification screenshot to confirm your app has launched.
|
|
474
483
|
**Prompt example**
|
|
475
484
|
|
|
476
485
|
```text
|
|
477
486
|
Take a screenshot of my app on Google Pixel 6 with Android 12 while testing on App Automate. App file path: /Users/xyz/app-debug.apk
|
|
478
487
|
```
|
|
479
488
|
|
|
480
|
-
|
|
489
|
+
24. `runAppTestsOnBrowserStack` — Run pre-built native mobile test suites (Espresso/XCUITest) by direct upload of compiled .apk/.ipa test files.
|
|
481
490
|
**Prompt example**
|
|
482
491
|
|
|
483
492
|
```text
|
|
484
493
|
Run Espresso tests from /tests/checkout.zip on Galaxy S21 and Pixel 6 with Android 12. App path is /apps/beta-release.apk under project 'Checkout Flow'
|
|
485
494
|
```
|
|
486
495
|
|
|
487
|
-
|
|
496
|
+
25. `setupBrowserStackAppAutomateTests` — Set up BrowserStack App Automate SDK integration for Appium-based mobile app testing.
|
|
488
497
|
**Prompt example**
|
|
489
498
|
|
|
490
499
|
```text
|
|
@@ -495,35 +504,35 @@ As of now we support 44 tools.
|
|
|
495
504
|
|
|
496
505
|
## ♿ Accessibility
|
|
497
506
|
|
|
498
|
-
|
|
507
|
+
26. `accessibilityExpert` — Ask the A11y Expert (WCAG 2.0/2.1/2.2, mobile/web usability, best practices).
|
|
499
508
|
**Prompt example**
|
|
500
509
|
|
|
501
510
|
```text
|
|
502
511
|
What WCAG guidelines apply to form field error messages on mobile web?
|
|
503
512
|
```
|
|
504
513
|
|
|
505
|
-
|
|
514
|
+
27. `startAccessibilityScan` — Start a web accessibility scan and retrieve a local CSV report path.
|
|
506
515
|
**Prompt example**
|
|
507
516
|
|
|
508
517
|
```text
|
|
509
518
|
Run accessibility scan for "www.example.com"
|
|
510
519
|
```
|
|
511
520
|
|
|
512
|
-
|
|
521
|
+
28. `createAccessibilityAuthConfig` — Create an authentication configuration (form-based or basic) for accessibility scans behind a login.
|
|
513
522
|
**Prompt example**
|
|
514
523
|
|
|
515
524
|
```text
|
|
516
525
|
Create a basic-auth accessibility config named 'site-login' for https://www.example.com with username testuser and password <password>
|
|
517
526
|
```
|
|
518
527
|
|
|
519
|
-
|
|
528
|
+
29. `getAccessibilityAuthConfig` — Retrieve an existing accessibility authentication configuration by ID.
|
|
520
529
|
**Prompt example**
|
|
521
530
|
|
|
522
531
|
```text
|
|
523
532
|
Get accessibility auth config with ID <config-id>
|
|
524
533
|
```
|
|
525
534
|
|
|
526
|
-
|
|
535
|
+
30. `fetchAccessibilityIssues` — Fetch accessibility issues from a completed scan, with pagination support.
|
|
527
536
|
**Prompt example**
|
|
528
537
|
|
|
529
538
|
```text
|
|
@@ -534,49 +543,49 @@ As of now we support 44 tools.
|
|
|
534
543
|
|
|
535
544
|
## 🎨 Percy Visual Testing
|
|
536
545
|
|
|
537
|
-
|
|
546
|
+
31. `percyVisualTestIntegrationAgent` — Integrate Percy visual testing into a new project and demonstrate visual change detection with a step-by-step simulation.
|
|
538
547
|
**Prompt example**
|
|
539
548
|
|
|
540
549
|
```text
|
|
541
550
|
Integrate Percy for this project
|
|
542
551
|
```
|
|
543
552
|
|
|
544
|
-
|
|
553
|
+
32. `expandPercyVisualTesting` — Set up or expand Percy visual testing coverage for existing projects (Percy Web Standalone and Percy Automate).
|
|
545
554
|
**Prompt example**
|
|
546
555
|
|
|
547
556
|
```text
|
|
548
557
|
Expand Percy coverage for this project
|
|
549
558
|
```
|
|
550
559
|
|
|
551
|
-
|
|
560
|
+
33. `addPercySnapshotCommands` — Add Percy snapshot commands to the specified test files. _(not available in Remote MCP)_
|
|
552
561
|
**Prompt example**
|
|
553
562
|
|
|
554
563
|
```text
|
|
555
564
|
Add Percy snapshot commands to my Cypress test files
|
|
556
565
|
```
|
|
557
566
|
|
|
558
|
-
|
|
567
|
+
34. `listTestFiles` — List all test files for a given set of directories. _(not available in Remote MCP)_
|
|
559
568
|
**Prompt example**
|
|
560
569
|
|
|
561
570
|
```text
|
|
562
571
|
List the test files under my ./tests directory
|
|
563
572
|
```
|
|
564
573
|
|
|
565
|
-
|
|
574
|
+
35. `runPercyScan` — Run a Percy visual test scan. _(not available in Remote MCP)_
|
|
566
575
|
**Prompt example**
|
|
567
576
|
|
|
568
577
|
```text
|
|
569
578
|
Run this Percy build
|
|
570
579
|
```
|
|
571
580
|
|
|
572
|
-
|
|
581
|
+
36. `fetchPercyChanges` — Retrieve and summarize visual changes detected by Percy AI between the latest and previous builds.
|
|
573
582
|
**Prompt example**
|
|
574
583
|
|
|
575
584
|
```text
|
|
576
585
|
Summarize the visual changes Percy detected in my latest build
|
|
577
586
|
```
|
|
578
587
|
|
|
579
|
-
|
|
588
|
+
37. `managePercyBuildApproval` — Approve or reject a Percy build.
|
|
580
589
|
**Prompt example**
|
|
581
590
|
|
|
582
591
|
```text
|
|
@@ -587,61 +596,68 @@ As of now we support 44 tools.
|
|
|
587
596
|
|
|
588
597
|
## 🤖 BrowserStack AI Agents
|
|
589
598
|
|
|
590
|
-
|
|
599
|
+
38. `uploadProductRequirementFile` — Upload a PRD/screenshot/PDF and get a file mapping ID (used with `createTestCasesFromFile`). _(not available in Remote MCP)_
|
|
591
600
|
**Prompt example**
|
|
592
601
|
|
|
593
602
|
```text
|
|
594
603
|
Upload PRD from /Users/xyz/Desktop/login-flow.pdf and use BrowserStack AI to generate test cases
|
|
595
604
|
```
|
|
596
605
|
|
|
597
|
-
|
|
606
|
+
39. `createLCASteps` — Generate Low Code Automation (LCA) steps from a manual test case in Test Management.
|
|
598
607
|
**Prompt example**
|
|
599
608
|
|
|
600
609
|
```text
|
|
601
610
|
Convert the manual test case 'Add to Cart' in the 'Shopping App' project into LCA steps
|
|
602
611
|
```
|
|
603
612
|
|
|
604
|
-
|
|
613
|
+
40. `fetchSelfHealedSelectors` — Retrieve AI self-healed selectors (plus test source) to fix flaky tests caused by DOM changes.
|
|
605
614
|
**Prompt example**
|
|
606
615
|
|
|
607
616
|
```text
|
|
608
617
|
Fetch and fix flaky test selectors in Automate session ID session_9482 using MCP
|
|
609
618
|
```
|
|
610
619
|
|
|
611
|
-
|
|
620
|
+
41. `prepareSelfHealingPlan` — Build a self-healing edit plan that bundles locator pairs with test source for your LLM to apply. Does NOT modify files itself.
|
|
612
621
|
**Prompt example**
|
|
613
622
|
|
|
614
623
|
```text
|
|
615
624
|
Prepare a self-healing plan from the self-healed selectors for my build
|
|
616
625
|
```
|
|
617
626
|
|
|
618
|
-
|
|
627
|
+
42. `fetchRCA` — Fetch AI Root Cause Analysis for your failed Automate/App-Automate tests (by numeric test ID). Suggests fixes only; never auto-applies.
|
|
619
628
|
**Prompt example**
|
|
620
629
|
|
|
621
630
|
```text
|
|
622
631
|
Fetch the root cause analysis for failed test IDs 101 and 102 on BrowserStack
|
|
623
632
|
```
|
|
624
633
|
|
|
625
|
-
|
|
634
|
+
43. `getBuildId` — Get the BrowserStack build ID for a given project and build name, scoped to your builds.
|
|
626
635
|
**Prompt example**
|
|
627
636
|
|
|
628
637
|
```text
|
|
629
638
|
Get the build ID for build 'nightly-regression' in project 'Checkout Flow'
|
|
630
639
|
```
|
|
631
640
|
|
|
632
|
-
|
|
641
|
+
44. `listBuildId` — Get the latest build ID for a project and build name, across all users (no user filter).
|
|
633
642
|
**Prompt example**
|
|
634
643
|
|
|
635
644
|
```text
|
|
636
645
|
Get the latest build ID for build 'nightly-regression' in project 'Checkout Flow'
|
|
637
646
|
```
|
|
638
647
|
|
|
639
|
-
|
|
648
|
+
45. `listTestIds` — List the tests in a BrowserStack build (Automate or App Automate) with each test's `status` and `session_id`, optionally filtered by status (passed/failed/pending/skipped). The `session_id` feeds `getFailureLogs` and `fetchAutomationScreenshots` directly.
|
|
640
649
|
**Prompt example**
|
|
641
650
|
|
|
642
651
|
```text
|
|
643
652
|
List the failed test IDs from build UUID <your-build-uuid> on BrowserStack
|
|
644
653
|
```
|
|
654
|
+
46. `askBrowserStackAI` *(Alpha, limited availability)* — Hand a multi-step task to BrowserStack's agent in plain language; it decides which calls to make and returns the answer plus the steps it took. Covers Test Management and Test Reporting & Analytics. Anything that would change data pauses for your confirmation in your own client; deletes are refused outright. Requires the account to be enrolled — otherwise it returns an entitlement error and nothing runs.
|
|
655
|
+
**Prompt example**
|
|
656
|
+
|
|
657
|
+
```text
|
|
658
|
+
Find all payment test cases in project Shopping App and add the 'regression' tag to them
|
|
659
|
+
```
|
|
660
|
+
|
|
645
661
|
|
|
646
662
|
## 🚀 Remote MCP Server
|
|
647
663
|
|
package/dist/config.d.ts
CHANGED
|
@@ -11,7 +11,10 @@ export declare class Config {
|
|
|
11
11
|
readonly O11Y_TFA_RCA_BASE_URL: string;
|
|
12
12
|
readonly BROWSERSTACK_AUTOMATION_BASE_URL: string;
|
|
13
13
|
readonly BROWSERSTACK_O11Y_UI_BASE_URL: string;
|
|
14
|
-
|
|
14
|
+
readonly ASK_BROWSERSTACK_ALLOW_REMOTE_RELAY: boolean;
|
|
15
|
+
readonly ASK_BROWSERSTACK_ATLAS_URL: string | undefined;
|
|
16
|
+
readonly ASK_BROWSERSTACK_AUTH_TOKEN_URL: string | undefined;
|
|
17
|
+
constructor(DEV_MODE: boolean, browserstackLocalOptions: Record<string, any>, USE_OWN_LOCAL_BINARY_PROCESS: boolean, REMOTE_MCP: boolean, UPLOAD_BASE_DIR: string | undefined, O11Y_TFA_RCA_BASE_URL: string, BROWSERSTACK_AUTOMATION_BASE_URL: string, BROWSERSTACK_O11Y_UI_BASE_URL: string, ASK_BROWSERSTACK_ALLOW_REMOTE_RELAY: boolean, ASK_BROWSERSTACK_ATLAS_URL: string | undefined, ASK_BROWSERSTACK_AUTH_TOKEN_URL: string | undefined);
|
|
15
18
|
}
|
|
16
19
|
declare const config: Config;
|
|
17
20
|
export default config;
|
package/dist/config.js
CHANGED
|
@@ -47,7 +47,18 @@ export class Config {
|
|
|
47
47
|
O11Y_TFA_RCA_BASE_URL;
|
|
48
48
|
BROWSERSTACK_AUTOMATION_BASE_URL;
|
|
49
49
|
BROWSERSTACK_O11Y_UI_BASE_URL;
|
|
50
|
-
|
|
50
|
+
ASK_BROWSERSTACK_ALLOW_REMOTE_RELAY;
|
|
51
|
+
ASK_BROWSERSTACK_ATLAS_URL;
|
|
52
|
+
ASK_BROWSERSTACK_AUTH_TOKEN_URL;
|
|
53
|
+
constructor(DEV_MODE, browserstackLocalOptions, USE_OWN_LOCAL_BINARY_PROCESS, REMOTE_MCP, UPLOAD_BASE_DIR, O11Y_TFA_RCA_BASE_URL, BROWSERSTACK_AUTOMATION_BASE_URL, BROWSERSTACK_O11Y_UI_BASE_URL,
|
|
54
|
+
// askBrowserStackAI's process-startup settings. Declared here rather than read from
|
|
55
|
+
// process.env inside src/tools/, per rules/tool-design.md — and so the remote wrapper,
|
|
56
|
+
// which only forwards env it knows about, has one place to look.
|
|
57
|
+
//
|
|
58
|
+
// ASK_BROWSERSTACK_DISABLED is deliberately NOT here: it is a kill switch, and reading
|
|
59
|
+
// it per call keeps it effective without a restart. Fixing it at boot would mean a pod
|
|
60
|
+
// roll to disable the tool, which is slowest exactly when you need it fastest.
|
|
61
|
+
ASK_BROWSERSTACK_ALLOW_REMOTE_RELAY, ASK_BROWSERSTACK_ATLAS_URL, ASK_BROWSERSTACK_AUTH_TOKEN_URL) {
|
|
51
62
|
this.DEV_MODE = DEV_MODE;
|
|
52
63
|
this.browserstackLocalOptions = browserstackLocalOptions;
|
|
53
64
|
this.USE_OWN_LOCAL_BINARY_PROCESS = USE_OWN_LOCAL_BINARY_PROCESS;
|
|
@@ -56,6 +67,9 @@ export class Config {
|
|
|
56
67
|
this.O11Y_TFA_RCA_BASE_URL = O11Y_TFA_RCA_BASE_URL;
|
|
57
68
|
this.BROWSERSTACK_AUTOMATION_BASE_URL = BROWSERSTACK_AUTOMATION_BASE_URL;
|
|
58
69
|
this.BROWSERSTACK_O11Y_UI_BASE_URL = BROWSERSTACK_O11Y_UI_BASE_URL;
|
|
70
|
+
this.ASK_BROWSERSTACK_ALLOW_REMOTE_RELAY = ASK_BROWSERSTACK_ALLOW_REMOTE_RELAY;
|
|
71
|
+
this.ASK_BROWSERSTACK_ATLAS_URL = ASK_BROWSERSTACK_ATLAS_URL;
|
|
72
|
+
this.ASK_BROWSERSTACK_AUTH_TOKEN_URL = ASK_BROWSERSTACK_AUTH_TOKEN_URL;
|
|
59
73
|
}
|
|
60
74
|
}
|
|
61
75
|
const config = new Config(process.env.DEV_MODE === "true", browserstackLocalOptions, process.env.USE_OWN_LOCAL_BINARY_PROCESS === "true", process.env.REMOTE_MCP === "true", process.env.MCP_UPLOAD_BASE_DIR && process.env.MCP_UPLOAD_BASE_DIR.length > 0
|
|
@@ -69,5 +83,12 @@ const config = new Config(process.env.DEV_MODE === "true", browserstackLocalOpti
|
|
|
69
83
|
: DEFAULT_BROWSERSTACK_AUTOMATION_BASE_URL, process.env.BROWSERSTACK_O11Y_UI_BASE_URL &&
|
|
70
84
|
process.env.BROWSERSTACK_O11Y_UI_BASE_URL.length > 0
|
|
71
85
|
? process.env.BROWSERSTACK_O11Y_UI_BASE_URL
|
|
72
|
-
: DEFAULT_BROWSERSTACK_O11Y_UI_BASE_URL)
|
|
86
|
+
: DEFAULT_BROWSERSTACK_O11Y_UI_BASE_URL, (process.env.ASK_BROWSERSTACK_ALLOW_REMOTE_RELAY || "").toLowerCase() ===
|
|
87
|
+
"true", process.env.ASK_BROWSERSTACK_ATLAS_URL &&
|
|
88
|
+
process.env.ASK_BROWSERSTACK_ATLAS_URL.trim().length > 0
|
|
89
|
+
? process.env.ASK_BROWSERSTACK_ATLAS_URL
|
|
90
|
+
: undefined, process.env.ASK_BROWSERSTACK_AUTH_TOKEN_URL &&
|
|
91
|
+
process.env.ASK_BROWSERSTACK_AUTH_TOKEN_URL.trim().length > 0
|
|
92
|
+
? process.env.ASK_BROWSERSTACK_AUTH_TOKEN_URL
|
|
93
|
+
: undefined);
|
|
73
94
|
export default config;
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import "dotenv/config";
|
|
3
3
|
export { setLogger } from "./logger.js";
|
|
4
4
|
export { BrowserStackMcpServer } from "./server-factory.js";
|
|
5
|
-
export { trackMCP } from "./lib/instrumentation.js";
|
|
5
|
+
export { trackMCP, withToolCall } from "./lib/instrumentation.js";
|
|
6
|
+
export { instrumentToolLatency } from "./lib/tool-latency.js";
|
|
6
7
|
export { default as addTfaRcaCollaborationTools } from "./tools/tfa-rca-collaboration.js";
|
|
7
8
|
export declare const PackageJsonVersion: any;
|
package/dist/index.js
CHANGED
|
@@ -29,12 +29,16 @@ async function main() {
|
|
|
29
29
|
await mcpServer.getInstance().connect(transport);
|
|
30
30
|
}
|
|
31
31
|
main().catch(console.error);
|
|
32
|
-
// Ensure logs are flushed before exit
|
|
32
|
+
// Ensure logs are flushed before exit. Optional-called because an exit handler must not
|
|
33
|
+
// be the thing that throws: `flush` is absent from every `vi.mock("src/logger")` in the
|
|
34
|
+
// suite, so any test file that loads this module used to fail the run with
|
|
35
|
+
// `default.flush is not a function` depending on how vitest scheduled workers.
|
|
33
36
|
process.on("exit", () => {
|
|
34
|
-
logger.flush();
|
|
37
|
+
logger.flush?.();
|
|
35
38
|
});
|
|
36
39
|
export { setLogger } from "./logger.js";
|
|
37
40
|
export { BrowserStackMcpServer } from "./server-factory.js";
|
|
38
|
-
export { trackMCP } from "./lib/instrumentation.js";
|
|
41
|
+
export { trackMCP, withToolCall } from "./lib/instrumentation.js";
|
|
42
|
+
export { instrumentToolLatency } from "./lib/tool-latency.js";
|
|
39
43
|
export { default as addTfaRcaCollaborationTools } from "./tools/tfa-rca-collaboration.js";
|
|
40
44
|
export const PackageJsonVersion = packageJson.version;
|
package/dist/lib/constants.d.ts
CHANGED
|
@@ -2,15 +2,18 @@ export declare const SessionType: {
|
|
|
2
2
|
readonly Automate: "automate";
|
|
3
3
|
readonly AppAutomate: "app-automate";
|
|
4
4
|
};
|
|
5
|
+
export declare const SessionVideoLogType: "video";
|
|
5
6
|
export declare const AutomateLogType: {
|
|
6
7
|
readonly NetworkLogs: "networkLogs";
|
|
7
8
|
readonly SessionLogs: "sessionLogs";
|
|
8
9
|
readonly ConsoleLogs: "consoleLogs";
|
|
10
|
+
readonly Video: "video";
|
|
9
11
|
};
|
|
10
12
|
export declare const AppAutomateLogType: {
|
|
11
13
|
readonly DeviceLogs: "deviceLogs";
|
|
12
14
|
readonly AppiumLogs: "appiumLogs";
|
|
13
15
|
readonly CrashLogs: "crashLogs";
|
|
16
|
+
readonly Video: "video";
|
|
14
17
|
};
|
|
15
18
|
export type SessionType = (typeof SessionType)[keyof typeof SessionType];
|
|
16
19
|
export type AutomateLogType = (typeof AutomateLogType)[keyof typeof AutomateLogType];
|
package/dist/lib/constants.js
CHANGED
|
@@ -2,13 +2,16 @@ export const SessionType = {
|
|
|
2
2
|
Automate: "automate",
|
|
3
3
|
AppAutomate: "app-automate",
|
|
4
4
|
};
|
|
5
|
+
export const SessionVideoLogType = "video";
|
|
5
6
|
export const AutomateLogType = {
|
|
6
7
|
NetworkLogs: "networkLogs",
|
|
7
8
|
SessionLogs: "sessionLogs",
|
|
8
9
|
ConsoleLogs: "consoleLogs",
|
|
10
|
+
Video: SessionVideoLogType,
|
|
9
11
|
};
|
|
10
12
|
export const AppAutomateLogType = {
|
|
11
13
|
DeviceLogs: "deviceLogs",
|
|
12
14
|
AppiumLogs: "appiumLogs",
|
|
13
15
|
CrashLogs: "crashLogs",
|
|
16
|
+
Video: SessionVideoLogType,
|
|
14
17
|
};
|
|
@@ -1,4 +1,16 @@
|
|
|
1
|
-
export
|
|
1
|
+
export type ClientInfo = {
|
|
2
2
|
name?: string;
|
|
3
3
|
version?: string;
|
|
4
|
-
}
|
|
4
|
+
};
|
|
5
|
+
export type ToolOutcome = "ok" | "error_result" | "threw";
|
|
6
|
+
/**
|
|
7
|
+
* Inside `withToolCall` this only records into the call's context; the single row is
|
|
8
|
+
* written when the handler settles. Outside one (`started` heartbeat, unwrapped host
|
|
9
|
+
* tools) it posts a row immediately, as before.
|
|
10
|
+
*/
|
|
11
|
+
export declare function trackMCP(toolName: string, clientInfo: ClientInfo, error?: unknown, config?: any): void;
|
|
12
|
+
/**
|
|
13
|
+
* Runs a tool handler and writes one MCPInstrumentation row when it settles: success,
|
|
14
|
+
* duration_ms, outcome (ok / error_result / threw), error fields on failure.
|
|
15
|
+
*/
|
|
16
|
+
export declare function withToolCall<T>(toolName: string, getClientInfo: () => ClientInfo, config: any, fn: () => Promise<T> | T): Promise<T>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
1
2
|
import logger from "../logger.js";
|
|
2
3
|
import { getBrowserStackAuth } from "./get-auth.js";
|
|
3
4
|
import { createRequire } from "module";
|
|
@@ -5,42 +6,31 @@ const require = createRequire(import.meta.url);
|
|
|
5
6
|
const packageJson = require("../../package.json");
|
|
6
7
|
import { apiClient } from "./apiClient.js";
|
|
7
8
|
import globalConfig from "../config.js";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
else {
|
|
17
|
-
logger.info("Client connected: unknown client");
|
|
18
|
-
}
|
|
19
|
-
const event = {
|
|
20
|
-
event_type: "MCPInstrumentation",
|
|
21
|
-
event_properties: {
|
|
22
|
-
mcp_version: packageJson.version,
|
|
23
|
-
tool_name: toolName,
|
|
24
|
-
mcp_client: mcpClient,
|
|
25
|
-
success: isSuccess,
|
|
26
|
-
is_remote: globalConfig.REMOTE_MCP,
|
|
27
|
-
},
|
|
9
|
+
const INSTRUMENTATION_ENDPOINT = "https://api.browserstack.com/sdk/v1/event";
|
|
10
|
+
function baseProperties(toolName, clientInfo) {
|
|
11
|
+
return {
|
|
12
|
+
mcp_version: packageJson.version,
|
|
13
|
+
tool_name: toolName,
|
|
14
|
+
mcp_client: clientInfo?.name || "unknown",
|
|
15
|
+
node_version: process.versions.node,
|
|
16
|
+
is_remote: globalConfig.REMOTE_MCP,
|
|
28
17
|
};
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
18
|
+
}
|
|
19
|
+
function errorProperties(error) {
|
|
20
|
+
return {
|
|
21
|
+
error_message: error instanceof Error ? error.message : String(error),
|
|
22
|
+
error_type: error instanceof Error ? error.constructor.name : "Unknown",
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function sendEvent(event, config) {
|
|
26
|
+
let authHeader;
|
|
37
27
|
if (config) {
|
|
38
28
|
const authString = getBrowserStackAuth(config);
|
|
39
29
|
authHeader = `Basic ${Buffer.from(authString).toString("base64")}`;
|
|
40
30
|
}
|
|
41
31
|
apiClient
|
|
42
32
|
.post({
|
|
43
|
-
url:
|
|
33
|
+
url: INSTRUMENTATION_ENDPOINT,
|
|
44
34
|
body: event,
|
|
45
35
|
headers: {
|
|
46
36
|
"Content-Type": "application/json",
|
|
@@ -51,3 +41,88 @@ export function trackMCP(toolName, clientInfo, error, config) {
|
|
|
51
41
|
})
|
|
52
42
|
.catch(() => { });
|
|
53
43
|
}
|
|
44
|
+
const callContext = new AsyncLocalStorage();
|
|
45
|
+
/**
|
|
46
|
+
* Inside `withToolCall` this only records into the call's context; the single row is
|
|
47
|
+
* written when the handler settles. Outside one (`started` heartbeat, unwrapped host
|
|
48
|
+
* tools) it posts a row immediately, as before.
|
|
49
|
+
*/
|
|
50
|
+
export function trackMCP(toolName, clientInfo, error, config) {
|
|
51
|
+
const ctx = callContext.getStore();
|
|
52
|
+
if (ctx) {
|
|
53
|
+
if (clientInfo?.name && !ctx.clientInfo?.name)
|
|
54
|
+
ctx.clientInfo = clientInfo;
|
|
55
|
+
if (config && !ctx.config)
|
|
56
|
+
ctx.config = config;
|
|
57
|
+
if (error)
|
|
58
|
+
ctx.error = error;
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (clientInfo?.name) {
|
|
62
|
+
logger.info(`Client connected: ${clientInfo.name} (version: ${clientInfo.version})`);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
logger.info("Client connected: unknown client");
|
|
66
|
+
}
|
|
67
|
+
const event = {
|
|
68
|
+
event_type: "MCPInstrumentation",
|
|
69
|
+
event_properties: {
|
|
70
|
+
...baseProperties(toolName, clientInfo),
|
|
71
|
+
success: !error,
|
|
72
|
+
...(error ? errorProperties(error) : {}),
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
sendEvent(event, config);
|
|
76
|
+
}
|
|
77
|
+
function isErrorResult(result) {
|
|
78
|
+
return (typeof result === "object" &&
|
|
79
|
+
result !== null &&
|
|
80
|
+
result.isError === true);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Runs a tool handler and writes one MCPInstrumentation row when it settles: success,
|
|
84
|
+
* duration_ms, outcome (ok / error_result / threw), error fields on failure.
|
|
85
|
+
*/
|
|
86
|
+
export async function withToolCall(toolName, getClientInfo, config, fn) {
|
|
87
|
+
const ctx = { toolName, clientInfo: {}, config };
|
|
88
|
+
const startedAt = performance.now();
|
|
89
|
+
let outcome = "ok";
|
|
90
|
+
try {
|
|
91
|
+
const result = await callContext.run(ctx, fn);
|
|
92
|
+
if (isErrorResult(result))
|
|
93
|
+
outcome = "error_result";
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
outcome = "threw";
|
|
98
|
+
ctx.error ??= error;
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
finally {
|
|
102
|
+
try {
|
|
103
|
+
let clientInfo = ctx.clientInfo;
|
|
104
|
+
try {
|
|
105
|
+
const live = getClientInfo();
|
|
106
|
+
if (live?.name)
|
|
107
|
+
clientInfo = live;
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
/* client info is optional */
|
|
111
|
+
}
|
|
112
|
+
const event = {
|
|
113
|
+
event_type: "MCPInstrumentation",
|
|
114
|
+
event_properties: {
|
|
115
|
+
...baseProperties(toolName, clientInfo),
|
|
116
|
+
success: ctx.error === undefined && outcome !== "threw",
|
|
117
|
+
duration_ms: Math.max(0, Math.round(performance.now() - startedAt)),
|
|
118
|
+
outcome,
|
|
119
|
+
...(ctx.error !== undefined ? errorProperties(ctx.error) : {}),
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
sendEvent(event, ctx.config ?? config);
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
/* telemetry must never affect the call */
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function nodeUpgradeNotice(version?: string): string;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
const RECOMMENDED_NODE_MAJOR = 22;
|
|
2
|
+
export function nodeUpgradeNotice(version = process.versions.node) {
|
|
3
|
+
const major = Number(version.split(".")[0]) || 0;
|
|
4
|
+
if (major >= RECOMMENDED_NODE_MAJOR)
|
|
5
|
+
return "";
|
|
6
|
+
return (`⚠️ Please use Node version > ${RECOMMENDED_NODE_MAJOR - 1}.x.x ` +
|
|
7
|
+
`(Node ${RECOMMENDED_NODE_MAJOR} LTS recommended). This server is running on ` +
|
|
8
|
+
`Node ${version}; older versions will be unsupported.`);
|
|
9
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { RegisteredTool } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { ClientInfo } from "./instrumentation.js";
|
|
3
|
+
/** Wraps every function handler in `withToolCall`. Idempotent; skips task-style handlers. */
|
|
4
|
+
export declare function instrumentToolLatency(tools: Record<string, RegisteredTool>, getClientInfo: () => ClientInfo, config?: unknown): void;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { withToolCall } from "./instrumentation.js";
|
|
2
|
+
const WRAPPED = Symbol.for("browserstack.mcp.latencyWrapped");
|
|
3
|
+
/** Wraps every function handler in `withToolCall`. Idempotent; skips task-style handlers. */
|
|
4
|
+
export function instrumentToolLatency(tools, getClientInfo, config) {
|
|
5
|
+
for (const [name, tool] of Object.entries(tools)) {
|
|
6
|
+
const inner = tool.handler;
|
|
7
|
+
if (typeof inner !== "function")
|
|
8
|
+
continue;
|
|
9
|
+
if (inner[WRAPPED])
|
|
10
|
+
continue;
|
|
11
|
+
const wrapped = (...args) => withToolCall(name, getClientInfo, config, () => inner(...args));
|
|
12
|
+
wrapped[WRAPPED] = true;
|
|
13
|
+
// Direct assignment: tool.update() would also fire tools/list_changed.
|
|
14
|
+
tool.handler = wrapped;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wrap untrusted external content before it is returned into the calling LLM's
|
|
3
|
+
* context. "Untrusted" = anything the server did not author itself: RAG chunks,
|
|
4
|
+
* device/console/session logs, backend AI-service output (RCA, Percy, TCG),
|
|
5
|
+
* scanned-page HTML, or text derived from user-uploaded files.
|
|
6
|
+
*
|
|
7
|
+
* The block is delimited with a per-call random nonce so injected content cannot
|
|
8
|
+
* forge the closing marker to break out, and prefixed with an instruction to
|
|
9
|
+
* treat the content strictly as data. Mitigates indirect prompt injection
|
|
10
|
+
*
|
|
11
|
+
* `source` is a short trusted label for the kind of data (e.g. "device logs").
|
|
12
|
+
* Pass a string literal only — never interpolate external/untrusted data into
|
|
13
|
+
* it, since it appears outside the quarantined block.
|
|
14
|
+
*/
|
|
15
|
+
export declare function wrapUntrusted(source: string, content: string): string;
|