@browserstack/mcp-server 1.0.14 → 1.1.0

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.
Files changed (54) hide show
  1. package/dist/config.js +6 -2
  2. package/dist/index.js +34 -31
  3. package/dist/lib/api.js +9 -3
  4. package/dist/lib/constants.js +6 -3
  5. package/dist/lib/device-cache.js +21 -14
  6. package/dist/lib/error.js +6 -3
  7. package/dist/lib/fuzzy.js +4 -1
  8. package/dist/lib/inmemory-store.js +4 -1
  9. package/dist/lib/instrumentation.js +18 -12
  10. package/dist/lib/local.js +35 -27
  11. package/dist/lib/utils.js +15 -6
  12. package/dist/logger.js +6 -4
  13. package/dist/tools/accessibility.js +54 -18
  14. package/dist/tools/accessiblity-utils/report-fetcher.js +35 -0
  15. package/dist/tools/accessiblity-utils/report-parser.js +57 -0
  16. package/dist/tools/accessiblity-utils/scanner.js +87 -0
  17. package/dist/tools/appautomate-utils/appautomate.js +27 -17
  18. package/dist/tools/appautomate.js +35 -29
  19. package/dist/tools/applive-utils/fuzzy-search.js +6 -3
  20. package/dist/tools/applive-utils/start-session.js +20 -14
  21. package/dist/tools/applive-utils/upload-app.js +51 -12
  22. package/dist/tools/applive.js +25 -18
  23. package/dist/tools/automate-utils/fetch-screenshots.js +14 -8
  24. package/dist/tools/automate.js +21 -14
  25. package/dist/tools/bstack-sdk.js +18 -14
  26. package/dist/tools/failurelogs-utils/app-automate.js +26 -15
  27. package/dist/tools/failurelogs-utils/automate.js +23 -13
  28. package/dist/tools/getFailureLogs.js +49 -42
  29. package/dist/tools/live-utils/desktop-filter.js +11 -8
  30. package/dist/tools/live-utils/mobile-filter.js +10 -7
  31. package/dist/tools/live-utils/start-session.js +23 -17
  32. package/dist/tools/live-utils/types.js +5 -2
  33. package/dist/tools/live-utils/version-resolver.js +4 -1
  34. package/dist/tools/live.js +29 -23
  35. package/dist/tools/observability.js +19 -12
  36. package/dist/tools/sdk-utils/constants.js +9 -3
  37. package/dist/tools/sdk-utils/instructions.js +9 -4
  38. package/dist/tools/sdk-utils/types.js +2 -1
  39. package/dist/tools/testmanagement-utils/TCG-utils/api.js +38 -26
  40. package/dist/tools/testmanagement-utils/TCG-utils/config.js +10 -5
  41. package/dist/tools/testmanagement-utils/TCG-utils/helpers.js +8 -3
  42. package/dist/tools/testmanagement-utils/TCG-utils/types.js +8 -5
  43. package/dist/tools/testmanagement-utils/add-test-result.js +24 -17
  44. package/dist/tools/testmanagement-utils/create-project-folder.js +28 -21
  45. package/dist/tools/testmanagement-utils/create-testcase.js +38 -30
  46. package/dist/tools/testmanagement-utils/create-testrun.js +30 -23
  47. package/dist/tools/testmanagement-utils/list-testcases.js +22 -15
  48. package/dist/tools/testmanagement-utils/list-testruns.js +19 -12
  49. package/dist/tools/testmanagement-utils/testcase-from-file.js +22 -16
  50. package/dist/tools/testmanagement-utils/update-testrun.js +22 -15
  51. package/dist/tools/testmanagement-utils/upload-file.js +29 -22
  52. package/dist/tools/testmanagement.js +76 -61
  53. package/package.json +4 -2
  54. package/dist/tools/accessiblity-utils/accessibility.js +0 -74
package/dist/config.js CHANGED
@@ -1,8 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Config = void 0;
1
4
  if (!process.env.BROWSERSTACK_ACCESS_KEY ||
2
5
  !process.env.BROWSERSTACK_USERNAME) {
3
6
  throw new Error("Unable to start MCP server. Please set the BROWSERSTACK_ACCESS_KEY and BROWSERSTACK_USERNAME environment variables. Go to https://www.browserstack.com/accounts/profile/details to access them");
4
7
  }
5
- export class Config {
8
+ class Config {
6
9
  browserstackUsername;
7
10
  browserstackAccessKey;
8
11
  DEV_MODE;
@@ -12,5 +15,6 @@ export class Config {
12
15
  this.DEV_MODE = DEV_MODE;
13
16
  }
14
17
  }
18
+ exports.Config = Config;
15
19
  const config = new Config(process.env.BROWSERSTACK_USERNAME, process.env.BROWSERSTACK_ACCESS_KEY, process.env.DEV_MODE === "true");
16
- export default config;
20
+ exports.default = config;
package/dist/index.js CHANGED
@@ -1,46 +1,49 @@
1
1
  #!/usr/bin/env node
2
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
- import packageJson from "../package.json" with { type: "json" };
5
- import "dotenv/config";
6
- import logger from "./logger.js";
7
- import addSDKTools from "./tools/bstack-sdk.js";
8
- import addAppLiveTools from "./tools/applive.js";
9
- import addObservabilityTools from "./tools/observability.js";
10
- import addBrowserLiveTools from "./tools/live.js";
11
- import addAccessibilityTools from "./tools/accessibility.js";
12
- import addTestManagementTools from "./tools/testmanagement.js";
13
- import addAppAutomationTools from "./tools/appautomate.js";
14
- import addFailureLogsTools from "./tools/getFailureLogs.js";
15
- import addAutomateTools from "./tools/automate.js";
16
- import { trackMCP } from "./lib/instrumentation.js";
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
8
+ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
9
+ const package_json_1 = __importDefault(require("../package.json"));
10
+ require("dotenv/config");
11
+ const logger_js_1 = __importDefault(require("./logger.js"));
12
+ const bstack_sdk_js_1 = __importDefault(require("./tools/bstack-sdk.js"));
13
+ const applive_js_1 = __importDefault(require("./tools/applive.js"));
14
+ const live_js_1 = __importDefault(require("./tools/live.js"));
15
+ const accessibility_js_1 = __importDefault(require("./tools/accessibility.js"));
16
+ const testmanagement_js_1 = __importDefault(require("./tools/testmanagement.js"));
17
+ const appautomate_js_1 = __importDefault(require("./tools/appautomate.js"));
18
+ const getFailureLogs_js_1 = __importDefault(require("./tools/getFailureLogs.js"));
19
+ const automate_js_1 = __importDefault(require("./tools/automate.js"));
20
+ const instrumentation_js_1 = require("./lib/instrumentation.js");
17
21
  function registerTools(server) {
18
- addSDKTools(server);
19
- addAppLiveTools(server);
20
- addBrowserLiveTools(server);
21
- addObservabilityTools(server);
22
- addAccessibilityTools(server);
23
- addTestManagementTools(server);
24
- addAppAutomationTools(server);
25
- addFailureLogsTools(server);
26
- addAutomateTools(server);
22
+ (0, bstack_sdk_js_1.default)(server);
23
+ (0, applive_js_1.default)(server);
24
+ (0, live_js_1.default)(server);
25
+ (0, accessibility_js_1.default)(server);
26
+ (0, testmanagement_js_1.default)(server);
27
+ (0, appautomate_js_1.default)(server);
28
+ (0, getFailureLogs_js_1.default)(server);
29
+ (0, automate_js_1.default)(server);
27
30
  }
28
31
  // Create an MCP server
29
- const server = new McpServer({
32
+ const server = new mcp_js_1.McpServer({
30
33
  name: "BrowserStack MCP Server",
31
- version: packageJson.version,
34
+ version: package_json_1.default.version,
32
35
  });
33
36
  registerTools(server);
34
37
  async function main() {
35
- logger.info("Launching BrowserStack MCP server, version %s", packageJson.version);
38
+ logger_js_1.default.info("Launching BrowserStack MCP server, version %s", package_json_1.default.version);
36
39
  // Start receiving messages on stdin and sending messages on stdout
37
- const transport = new StdioServerTransport();
40
+ const transport = new stdio_js_1.StdioServerTransport();
38
41
  await server.connect(transport);
39
- logger.info("MCP server started successfully");
40
- trackMCP("started", server.server.getClientVersion());
42
+ logger_js_1.default.info("MCP server started successfully");
43
+ (0, instrumentation_js_1.trackMCP)("started", server.server.getClientVersion());
41
44
  }
42
45
  main().catch(console.error);
43
46
  // Ensure logs are flushed before exit
44
47
  process.on("exit", () => {
45
- logger.flush();
48
+ logger_js_1.default.flush();
46
49
  });
package/dist/lib/api.js CHANGED
@@ -1,9 +1,15 @@
1
- import config from "../config.js";
2
- export async function getLatestO11YBuildInfo(buildName, projectName) {
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getLatestO11YBuildInfo = getLatestO11YBuildInfo;
7
+ const config_js_1 = __importDefault(require("../config.js"));
8
+ async function getLatestO11YBuildInfo(buildName, projectName) {
3
9
  const buildsUrl = `https://api-observability.browserstack.com/ext/v1/builds/latest?build_name=${encodeURIComponent(buildName)}&project_name=${encodeURIComponent(projectName)}`;
4
10
  const buildsResponse = await fetch(buildsUrl, {
5
11
  headers: {
6
- Authorization: `Basic ${Buffer.from(`${config.browserstackUsername}:${config.browserstackAccessKey}`).toString("base64")}`,
12
+ Authorization: `Basic ${Buffer.from(`${config_js_1.default.browserstackUsername}:${config_js_1.default.browserstackAccessKey}`).toString("base64")}`,
7
13
  },
8
14
  });
9
15
  if (!buildsResponse.ok) {
@@ -1,13 +1,16 @@
1
- export const SessionType = {
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AppAutomateLogType = exports.AutomateLogType = exports.SessionType = void 0;
4
+ exports.SessionType = {
2
5
  Automate: "automate",
3
6
  AppAutomate: "app-automate",
4
7
  };
5
- export const AutomateLogType = {
8
+ exports.AutomateLogType = {
6
9
  NetworkLogs: "networkLogs",
7
10
  SessionLogs: "sessionLogs",
8
11
  ConsoleLogs: "consoleLogs",
9
12
  };
10
- export const AppAutomateLogType = {
13
+ exports.AppAutomateLogType = {
11
14
  DeviceLogs: "deviceLogs",
12
15
  AppiumLogs: "appiumLogs",
13
16
  CrashLogs: "crashLogs",
@@ -1,15 +1,22 @@
1
- import fs from "fs";
2
- import os from "os";
3
- import path from "path";
4
- const CACHE_DIR = path.join(os.homedir(), ".browserstack", "combined_cache");
5
- const CACHE_FILE = path.join(CACHE_DIR, "data.json");
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.BrowserStackProducts = void 0;
7
+ exports.getDevicesAndBrowsers = getDevicesAndBrowsers;
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const os_1 = __importDefault(require("os"));
10
+ const path_1 = __importDefault(require("path"));
11
+ const CACHE_DIR = path_1.default.join(os_1.default.homedir(), ".browserstack", "combined_cache");
12
+ const CACHE_FILE = path_1.default.join(CACHE_DIR, "data.json");
6
13
  const TTL_MS = 24 * 60 * 60 * 1000; // 1 day
7
- export var BrowserStackProducts;
14
+ var BrowserStackProducts;
8
15
  (function (BrowserStackProducts) {
9
16
  BrowserStackProducts["LIVE"] = "live";
10
17
  BrowserStackProducts["APP_LIVE"] = "app_live";
11
18
  BrowserStackProducts["APP_AUTOMATE"] = "app_automate";
12
- })(BrowserStackProducts || (BrowserStackProducts = {}));
19
+ })(BrowserStackProducts || (exports.BrowserStackProducts = BrowserStackProducts = {}));
13
20
  const URLS = {
14
21
  [BrowserStackProducts.LIVE]: "https://www.browserstack.com/list-of-browsers-and-platforms/live.json",
15
22
  [BrowserStackProducts.APP_LIVE]: "https://www.browserstack.com/list-of-browsers-and-platforms/app_live.json",
@@ -18,16 +25,16 @@ const URLS = {
18
25
  /**
19
26
  * Fetches and caches BrowserStack datasets (live + app_live + app_automate) with a shared TTL.
20
27
  */
21
- export async function getDevicesAndBrowsers(type) {
22
- if (!fs.existsSync(CACHE_DIR)) {
23
- fs.mkdirSync(CACHE_DIR, { recursive: true });
28
+ async function getDevicesAndBrowsers(type) {
29
+ if (!fs_1.default.existsSync(CACHE_DIR)) {
30
+ fs_1.default.mkdirSync(CACHE_DIR, { recursive: true });
24
31
  }
25
32
  let cache = {};
26
- if (fs.existsSync(CACHE_FILE)) {
27
- const stats = fs.statSync(CACHE_FILE);
33
+ if (fs_1.default.existsSync(CACHE_FILE)) {
34
+ const stats = fs_1.default.statSync(CACHE_FILE);
28
35
  if (Date.now() - stats.mtimeMs < TTL_MS) {
29
36
  try {
30
- cache = JSON.parse(fs.readFileSync(CACHE_FILE, "utf8"));
37
+ cache = JSON.parse(fs_1.default.readFileSync(CACHE_FILE, "utf8"));
31
38
  if (cache[type]) {
32
39
  return cache[type];
33
40
  }
@@ -46,6 +53,6 @@ export async function getDevicesAndBrowsers(type) {
46
53
  cache = {
47
54
  [type]: data,
48
55
  };
49
- fs.writeFileSync(CACHE_FILE, JSON.stringify(cache), "utf8");
56
+ fs_1.default.writeFileSync(CACHE_FILE, JSON.stringify(cache), "utf8");
50
57
  return cache[type];
51
58
  }
package/dist/lib/error.js CHANGED
@@ -1,12 +1,15 @@
1
- import { AxiosError } from "axios";
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.formatAxiosError = formatAxiosError;
4
+ const axios_1 = require("axios");
2
5
  /**
3
6
  * Formats an AxiosError into a CallToolResult with an appropriate message.
4
7
  * @param err - The error object to format
5
8
  * @param defaultText - The fallback error message
6
9
  */
7
- export function formatAxiosError(err, defaultText) {
10
+ function formatAxiosError(err, defaultText) {
8
11
  let text = defaultText;
9
- if (err instanceof AxiosError && err.response?.data) {
12
+ if (err instanceof axios_1.AxiosError && err.response?.data) {
10
13
  const message = err.response.data.message ||
11
14
  err.response.data.error ||
12
15
  err.message ||
package/dist/lib/fuzzy.js CHANGED
@@ -1,3 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.customFuzzySearch = customFuzzySearch;
1
4
  // 1. Compute Levenshtein distance between two strings
2
5
  function levenshtein(a, b) {
3
6
  const dp = Array(a.length + 1)
@@ -36,7 +39,7 @@ function scoreItem(item, keys, queryTokens) {
36
39
  return best;
37
40
  }
38
41
  // 3. The search entrypoint
39
- export function customFuzzySearch(list, keys, query, limit = 5, maxDistance = 0.6) {
42
+ function customFuzzySearch(list, keys, query, limit = 5, maxDistance = 0.6) {
40
43
  const q = query.toLowerCase().trim();
41
44
  const queryTokens = q.split(/\s+/);
42
45
  return list
@@ -1 +1,4 @@
1
- export const signedUrlMap = new Map();
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.signedUrlMap = void 0;
4
+ exports.signedUrlMap = new Map();
@@ -1,10 +1,16 @@
1
- import logger from "../logger.js";
2
- import config from "../config.js";
3
- import packageJson from "../../package.json" with { type: "json" };
4
- import axios from "axios";
5
- export function trackMCP(toolName, clientInfo, error) {
6
- if (config.DEV_MODE) {
7
- logger.info("Tracking MCP is disabled in dev mode");
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.trackMCP = trackMCP;
7
+ const logger_js_1 = __importDefault(require("../logger.js"));
8
+ const config_js_1 = __importDefault(require("../config.js"));
9
+ const package_json_1 = __importDefault(require("../../package.json"));
10
+ const axios_1 = __importDefault(require("axios"));
11
+ function trackMCP(toolName, clientInfo, error) {
12
+ if (config_js_1.default.DEV_MODE) {
13
+ logger_js_1.default.info("Tracking MCP is disabled in dev mode");
8
14
  return;
9
15
  }
10
16
  const instrumentationEndpoint = "https://api.browserstack.com/sdk/v1/event";
@@ -12,15 +18,15 @@ export function trackMCP(toolName, clientInfo, error) {
12
18
  const mcpClient = clientInfo?.name || "unknown";
13
19
  // Log client information
14
20
  if (clientInfo?.name) {
15
- logger.info(`Client connected: ${clientInfo.name} (version: ${clientInfo.version})`);
21
+ logger_js_1.default.info(`Client connected: ${clientInfo.name} (version: ${clientInfo.version})`);
16
22
  }
17
23
  else {
18
- logger.info("Client connected: unknown client");
24
+ logger_js_1.default.info("Client connected: unknown client");
19
25
  }
20
26
  const event = {
21
27
  event_type: "MCPInstrumentation",
22
28
  event_properties: {
23
- mcp_version: packageJson.version,
29
+ mcp_version: package_json_1.default.version,
24
30
  tool_name: toolName,
25
31
  mcp_client: mcpClient,
26
32
  success: isSuccess,
@@ -33,11 +39,11 @@ export function trackMCP(toolName, clientInfo, error) {
33
39
  event.event_properties.error_type =
34
40
  error instanceof Error ? error.constructor.name : "Unknown";
35
41
  }
36
- axios
42
+ axios_1.default
37
43
  .post(instrumentationEndpoint, event, {
38
44
  headers: {
39
45
  "Content-Type": "application/json",
40
- Authorization: `Basic ${Buffer.from(`${config.browserstackUsername}:${config.browserstackAccessKey}`).toString("base64")}`,
46
+ Authorization: `Basic ${Buffer.from(`${config_js_1.default.browserstackUsername}:${config_js_1.default.browserstackAccessKey}`).toString("base64")}`,
41
47
  },
42
48
  timeout: 2000,
43
49
  })
package/dist/lib/local.js CHANGED
@@ -1,39 +1,47 @@
1
- import logger from "../logger.js";
2
- import { execSync } from "child_process";
3
- import { Local } from "browserstack-local";
4
- import config from "../config.js";
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.killExistingBrowserStackLocalProcesses = killExistingBrowserStackLocalProcesses;
7
+ exports.ensureLocalBinarySetup = ensureLocalBinarySetup;
8
+ exports.isLocalURL = isLocalURL;
9
+ const logger_js_1 = __importDefault(require("../logger.js"));
10
+ const child_process_1 = require("child_process");
11
+ const browserstack_local_1 = require("browserstack-local");
12
+ const config_js_1 = __importDefault(require("../config.js"));
5
13
  async function isBrowserStackLocalRunning() {
6
14
  // Check if BrowserStackLocal binary is already running
7
15
  try {
8
16
  if (process.platform === "win32") {
9
- const result = execSync('tasklist /FI "IMAGENAME eq BrowserStackLocal.exe"', {
17
+ const result = (0, child_process_1.execSync)('tasklist /FI "IMAGENAME eq BrowserStackLocal.exe"', {
10
18
  encoding: "utf8",
11
19
  });
12
20
  if (result.includes("BrowserStackLocal.exe")) {
13
- logger.info("BrowserStackLocal binary is already running");
21
+ logger_js_1.default.info("BrowserStackLocal binary is already running");
14
22
  return true;
15
23
  }
16
24
  }
17
25
  else {
18
- const result = execSync("pgrep -f BrowserStackLocal", {
26
+ const result = (0, child_process_1.execSync)("pgrep -f BrowserStackLocal", {
19
27
  encoding: "utf8",
20
28
  stdio: "pipe",
21
29
  }).toString();
22
30
  if (result) {
23
- logger.info("BrowserStackLocal binary is already running");
31
+ logger_js_1.default.info("BrowserStackLocal binary is already running");
24
32
  return true;
25
33
  }
26
34
  }
27
- logger.info("BrowserStackLocal binary is not running");
35
+ logger_js_1.default.info("BrowserStackLocal binary is not running");
28
36
  return false;
29
37
  }
30
38
  catch (error) {
31
- logger.info("Error checking BrowserStackLocal status, assuming not running ... " +
39
+ logger_js_1.default.info("Error checking BrowserStackLocal status, assuming not running ... " +
32
40
  error);
33
41
  return false;
34
42
  }
35
43
  }
36
- export async function killExistingBrowserStackLocalProcesses() {
44
+ async function killExistingBrowserStackLocalProcesses() {
37
45
  const isRunning = await isBrowserStackLocalRunning();
38
46
  if (!isRunning) {
39
47
  return;
@@ -42,50 +50,50 @@ export async function killExistingBrowserStackLocalProcesses() {
42
50
  try {
43
51
  if (process.platform === "win32") {
44
52
  // Check if process exists on Windows
45
- const checkResult = execSync('tasklist /FI "IMAGENAME eq BrowserStackLocal.exe"', { encoding: "utf8" });
53
+ const checkResult = (0, child_process_1.execSync)('tasklist /FI "IMAGENAME eq BrowserStackLocal.exe"', { encoding: "utf8" });
46
54
  if (checkResult.includes("BrowserStackLocal.exe")) {
47
- execSync("taskkill /F /IM BrowserStackLocal.exe", { stdio: "ignore" });
48
- logger.info("Successfully killed existing BrowserStackLocal processes");
55
+ (0, child_process_1.execSync)("taskkill /F /IM BrowserStackLocal.exe", { stdio: "ignore" });
56
+ logger_js_1.default.info("Successfully killed existing BrowserStackLocal processes");
49
57
  }
50
58
  }
51
59
  else {
52
60
  // Check if process exists on Unix-like systems
53
- const checkResult = execSync("pgrep -f BrowserStackLocal", {
61
+ const checkResult = (0, child_process_1.execSync)("pgrep -f BrowserStackLocal", {
54
62
  encoding: "utf8",
55
63
  stdio: "pipe",
56
64
  }).toString();
57
65
  if (checkResult) {
58
- execSync("pkill -f BrowserStackLocal", { stdio: "ignore" });
59
- logger.info("Successfully killed existing BrowserStackLocal processes");
66
+ (0, child_process_1.execSync)("pkill -f BrowserStackLocal", { stdio: "ignore" });
67
+ logger_js_1.default.info("Successfully killed existing BrowserStackLocal processes");
60
68
  }
61
69
  }
62
70
  }
63
71
  catch (error) {
64
- logger.info(`Error checking/killing BrowserStackLocal processes: ${error}`);
72
+ logger_js_1.default.info(`Error checking/killing BrowserStackLocal processes: ${error}`);
65
73
  // Continue execution as there may not be any processes running
66
74
  }
67
75
  }
68
- export async function ensureLocalBinarySetup() {
69
- logger.info("Ensuring local binary setup as it is required for private URLs...");
70
- const localBinary = new Local();
76
+ async function ensureLocalBinarySetup() {
77
+ logger_js_1.default.info("Ensuring local binary setup as it is required for private URLs...");
78
+ const localBinary = new browserstack_local_1.Local();
71
79
  await killExistingBrowserStackLocalProcesses();
72
80
  return await new Promise((resolve, reject) => {
73
81
  localBinary.start({
74
- key: config.browserstackAccessKey,
75
- username: config.browserstackUsername,
82
+ key: config_js_1.default.browserstackAccessKey,
83
+ username: config_js_1.default.browserstackUsername,
76
84
  }, (error) => {
77
85
  if (error) {
78
- logger.error(`Unable to start BrowserStack Local... please check your credentials and try again. Error: ${error}`);
86
+ logger_js_1.default.error(`Unable to start BrowserStack Local... please check your credentials and try again. Error: ${error}`);
79
87
  reject(new Error(`Unable to configure local tunnel binary, please check your credentials and try again. Error: ${error}`));
80
88
  }
81
89
  else {
82
- logger.info("Successfully started BrowserStack Local");
90
+ logger_js_1.default.info("Successfully started BrowserStack Local");
83
91
  resolve();
84
92
  }
85
93
  });
86
94
  });
87
95
  }
88
- export function isLocalURL(url) {
96
+ function isLocalURL(url) {
89
97
  try {
90
98
  const urlObj = new URL(url);
91
99
  const hostname = urlObj.hostname.toLowerCase();
@@ -95,7 +103,7 @@ export function isLocalURL(url) {
95
103
  hostname.endsWith(".localhost"));
96
104
  }
97
105
  catch (error) {
98
- logger.error(`Error checking if URL is local: ${error}`);
106
+ logger_js_1.default.error(`Error checking if URL is local: ${error}`);
99
107
  return false;
100
108
  }
101
109
  }
package/dist/lib/utils.js CHANGED
@@ -1,11 +1,20 @@
1
- import sharp from "sharp";
2
- export function sanitizeUrlParam(param) {
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.sanitizeUrlParam = sanitizeUrlParam;
7
+ exports.maybeCompressBase64 = maybeCompressBase64;
8
+ exports.assertOkResponse = assertOkResponse;
9
+ exports.filterLinesByKeywords = filterLinesByKeywords;
10
+ const sharp_1 = __importDefault(require("sharp"));
11
+ function sanitizeUrlParam(param) {
3
12
  // Remove any characters that could be used for command injection
4
13
  return param.replace(/[;&|`$(){}[\]<>]/g, "");
5
14
  }
6
15
  const ONE_MB = 1048576;
7
16
  //Compresses a base64 image intelligently to keep it under 1 MB if needed.
8
- export async function maybeCompressBase64(base64) {
17
+ async function maybeCompressBase64(base64) {
9
18
  const buffer = Buffer.from(base64, "base64");
10
19
  if (buffer.length <= ONE_MB) {
11
20
  return base64;
@@ -13,10 +22,10 @@ export async function maybeCompressBase64(base64) {
13
22
  const sizeRatio = 1048576 / buffer.length;
14
23
  const estimatedQuality = Math.floor(sizeRatio * 100);
15
24
  const quality = Math.min(95, Math.max(30, estimatedQuality));
16
- const compressedBuffer = await sharp(buffer).png({ quality }).toBuffer();
25
+ const compressedBuffer = await (0, sharp_1.default)(buffer).png({ quality }).toBuffer();
17
26
  return compressedBuffer.toString("base64");
18
27
  }
19
- export async function assertOkResponse(response, action) {
28
+ async function assertOkResponse(response, action) {
20
29
  if (!response.ok) {
21
30
  if (response.status === 404) {
22
31
  throw new Error(`Invalid session ID for ${action}`);
@@ -24,7 +33,7 @@ export async function assertOkResponse(response, action) {
24
33
  throw new Error(`Failed to fetch logs for ${action}: ${response.statusText}`);
25
34
  }
26
35
  }
27
- export function filterLinesByKeywords(logText, keywords) {
36
+ function filterLinesByKeywords(logText, keywords) {
28
37
  return logText
29
38
  .split(/\r?\n/)
30
39
  .map((line) => line.trim())
package/dist/logger.js CHANGED
@@ -1,7 +1,9 @@
1
- import { pino } from "pino";
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const pino_1 = require("pino");
2
4
  let logger;
3
5
  if (process.env.NODE_ENV === "development") {
4
- logger = pino({
6
+ logger = (0, pino_1.pino)({
5
7
  level: "debug",
6
8
  transport: {
7
9
  targets: [
@@ -22,7 +24,7 @@ if (process.env.NODE_ENV === "development") {
22
24
  }
23
25
  else {
24
26
  // NULL logger
25
- logger = pino({
27
+ logger = (0, pino_1.pino)({
26
28
  level: "info",
27
29
  transport: {
28
30
  target: "pino/file",
@@ -32,4 +34,4 @@ else {
32
34
  },
33
35
  });
34
36
  }
35
- export default logger;
37
+ exports.default = logger;
@@ -1,33 +1,69 @@
1
- import { z } from "zod";
2
- import { startAccessibilityScan, } from "./accessiblity-utils/accessibility.js";
3
- import { trackMCP } from "../lib/instrumentation.js";
4
- async function runAccessibilityScan(name, pageURL) {
5
- const response = await startAccessibilityScan(name, [pageURL]);
6
- const scanId = response.data?.id;
7
- const scanRunId = response.data?.scanRunId;
8
- if (!scanId || !scanRunId) {
9
- throw new Error("Unable to start a accessibility scan, please try again later or open an issue on GitHub if the problem persists");
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = addAccessibilityTools;
4
+ const zod_1 = require("zod");
5
+ const scanner_js_1 = require("./accessiblity-utils/scanner.js");
6
+ const report_fetcher_js_1 = require("./accessiblity-utils/report-fetcher.js");
7
+ const instrumentation_js_1 = require("../lib/instrumentation.js");
8
+ const report_parser_js_1 = require("./accessiblity-utils/report-parser.js");
9
+ const scanner = new scanner_js_1.AccessibilityScanner();
10
+ const reportFetcher = new report_fetcher_js_1.AccessibilityReportFetcher();
11
+ async function runAccessibilityScan(name, pageURL, context) {
12
+ // Start scan
13
+ const startResp = await scanner.startScan(name, [pageURL]);
14
+ const scanId = startResp.data.id;
15
+ const scanRunId = startResp.data.scanRunId;
16
+ // Notify scan start
17
+ await context.sendNotification({
18
+ method: "notifications/progress",
19
+ params: {
20
+ progressToken: context._meta?.progressToken ?? "NOT_FOUND",
21
+ message: `Accessibility scan "${name}" started`,
22
+ progress: 0,
23
+ total: 100,
24
+ },
25
+ });
26
+ // Wait until scan completes
27
+ const status = await scanner.waitUntilComplete(scanId, scanRunId, context);
28
+ if (status !== "completed") {
29
+ return {
30
+ content: [
31
+ {
32
+ type: "text",
33
+ text: `❌ Accessibility scan "${name}" failed with status: ${status} , check the BrowserStack dashboard for more details [https://scanner.browserstack.com/site-scanner/scan-details/${name}].`,
34
+ isError: true,
35
+ },
36
+ ],
37
+ isError: true,
38
+ };
10
39
  }
40
+ // Fetch CSV report link
41
+ const reportLink = await reportFetcher.getReportLink(scanId, scanRunId);
42
+ const { records } = await (0, report_parser_js_1.parseAccessibilityReportFromCSV)(reportLink);
11
43
  return {
12
44
  content: [
13
45
  {
14
46
  type: "text",
15
- text: `Successfully queued accessibility scan, you will get a report via email within 5 minutes.`,
47
+ text: `✅ Accessibility scan "${name}" completed. check the BrowserStack dashboard for more details [https://scanner.browserstack.com/site-scanner/scan-details/${name}].`,
48
+ },
49
+ {
50
+ type: "text",
51
+ text: `Scan results: ${JSON.stringify(records, null, 2)}`,
16
52
  },
17
53
  ],
18
54
  };
19
55
  }
20
- export default function addAccessibilityTools(server) {
21
- server.tool("startAccessibilityScan", "Use this tool to start an accessibility scan for a list of URLs on BrowserStack.", {
22
- name: z.string().describe("Name of the accessibility scan"),
23
- pageURL: z.string().describe("The URL to scan for accessibility issues"),
24
- }, async (args) => {
56
+ function addAccessibilityTools(server) {
57
+ server.tool("startAccessibilityScan", "Start an accessibility scan via BrowserStack and retrieve a local CSV report path.", {
58
+ name: zod_1.z.string().describe("Name of the accessibility scan"),
59
+ pageURL: zod_1.z.string().describe("The URL to scan for accessibility issues"),
60
+ }, async (args, context) => {
25
61
  try {
26
- trackMCP("startAccessibilityScan", server.server.getClientVersion());
27
- return await runAccessibilityScan(args.name, args.pageURL);
62
+ (0, instrumentation_js_1.trackMCP)("startAccessibilityScan", server.server.getClientVersion());
63
+ return await runAccessibilityScan(args.name, args.pageURL, context);
28
64
  }
29
65
  catch (error) {
30
- trackMCP("startAccessibilityScan", server.server.getClientVersion(), error);
66
+ (0, instrumentation_js_1.trackMCP)("startAccessibilityScan", server.server.getClientVersion(), error);
31
67
  return {
32
68
  content: [
33
69
  {
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.AccessibilityReportFetcher = void 0;
7
+ const axios_1 = __importDefault(require("axios"));
8
+ const config_js_1 = __importDefault(require("../../config.js"));
9
+ class AccessibilityReportFetcher {
10
+ auth = {
11
+ username: config_js_1.default.browserstackUsername,
12
+ password: config_js_1.default.browserstackAccessKey,
13
+ };
14
+ async getReportLink(scanId, scanRunId) {
15
+ // Initiate CSV link generation
16
+ const initUrl = `https://api-accessibility.browserstack.com/api/website-scanner/v1/scans/${scanId}/scan_runs/issues?scan_run_id=${scanRunId}`;
17
+ const initResp = await axios_1.default.get(initUrl, {
18
+ auth: this.auth,
19
+ });
20
+ if (!initResp.data.success) {
21
+ throw new Error(`Failed to initiate report: ${initResp.data.error || initResp.data.data.message}`);
22
+ }
23
+ const taskId = initResp.data.data.task_id;
24
+ // Fetch the generated CSV link
25
+ const reportUrl = `https://api-accessibility.browserstack.com/api/website-scanner/v1/scans/${scanId}/scan_runs/issues?task_id=${encodeURIComponent(taskId)}`;
26
+ const reportResp = await axios_1.default.get(reportUrl, {
27
+ auth: this.auth,
28
+ });
29
+ if (!reportResp.data.success) {
30
+ throw new Error(`Failed to fetch report: ${reportResp.data.error}`);
31
+ }
32
+ return reportResp.data.data.reportLink;
33
+ }
34
+ }
35
+ exports.AccessibilityReportFetcher = AccessibilityReportFetcher;