@browserstack/mcp-server 1.1.9 → 1.2.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.
Files changed (124) hide show
  1. package/README.md +4 -1
  2. package/dist/config.d.ts +13 -0
  3. package/dist/config.js +4 -6
  4. package/dist/index.d.ts +4 -0
  5. package/dist/index.js +21 -31
  6. package/dist/lib/api.d.ts +2 -0
  7. package/dist/lib/api.js +10 -5
  8. package/dist/lib/apiClient.d.ts +31 -0
  9. package/dist/lib/apiClient.js +108 -0
  10. package/dist/lib/constants.d.ts +17 -0
  11. package/dist/lib/device-cache.d.ts +9 -0
  12. package/dist/lib/device-cache.js +3 -3
  13. package/dist/lib/error.d.ts +7 -0
  14. package/dist/lib/fuzzy.d.ts +1 -0
  15. package/dist/lib/get-auth.d.ts +2 -0
  16. package/dist/lib/get-auth.js +8 -0
  17. package/dist/lib/inmemory-store.d.ts +1 -0
  18. package/dist/lib/instrumentation.d.ts +4 -0
  19. package/dist/lib/instrumentation.js +8 -7
  20. package/dist/lib/local.d.ts +3 -0
  21. package/dist/lib/local.js +3 -3
  22. package/dist/lib/types.d.ts +4 -0
  23. package/dist/lib/types.js +1 -0
  24. package/dist/lib/utils.d.ts +4 -0
  25. package/dist/lib/version-resolver.d.ts +6 -0
  26. package/dist/logger.d.ts +3 -0
  27. package/dist/logger.js +20 -4
  28. package/dist/oninitialized.d.ts +2 -0
  29. package/dist/oninitialized.js +2 -7
  30. package/dist/server-factory.d.ts +25 -0
  31. package/dist/server-factory.js +70 -0
  32. package/dist/tools/accessibility.d.ts +3 -0
  33. package/dist/tools/accessibility.js +21 -12
  34. package/dist/tools/accessiblity-utils/accessibility-rag.d.ts +12 -0
  35. package/dist/tools/accessiblity-utils/accessibility-rag.js +22 -12
  36. package/dist/tools/accessiblity-utils/report-fetcher.d.ts +8 -0
  37. package/dist/tools/accessiblity-utils/report-fetcher.js +26 -16
  38. package/dist/tools/accessiblity-utils/report-parser.d.ts +23 -0
  39. package/dist/tools/accessiblity-utils/report-parser.js +3 -3
  40. package/dist/tools/accessiblity-utils/scanner.d.ts +25 -0
  41. package/dist/tools/accessiblity-utils/scanner.js +43 -24
  42. package/dist/tools/appautomate-utils/appautomate.d.ts +42 -0
  43. package/dist/tools/appautomate-utils/appautomate.js +63 -38
  44. package/dist/tools/appautomate-utils/types.d.ts +5 -0
  45. package/dist/tools/appautomate.d.ts +3 -0
  46. package/dist/tools/appautomate.js +24 -21
  47. package/dist/tools/applive-utils/device-search.d.ts +6 -0
  48. package/dist/tools/applive-utils/start-session.d.ts +15 -0
  49. package/dist/tools/applive-utils/start-session.js +11 -4
  50. package/dist/tools/applive-utils/types.d.ts +7 -0
  51. package/dist/tools/applive-utils/upload-app.d.ts +5 -0
  52. package/dist/tools/applive-utils/upload-app.js +8 -12
  53. package/dist/tools/applive-utils/version-utils.d.ts +4 -0
  54. package/dist/tools/applive.d.ts +13 -0
  55. package/dist/tools/applive.js +9 -7
  56. package/dist/tools/automate-utils/fetch-screenshots.d.ts +6 -0
  57. package/dist/tools/automate-utils/fetch-screenshots.js +16 -12
  58. package/dist/tools/automate.d.ts +9 -0
  59. package/dist/tools/automate.js +9 -7
  60. package/dist/tools/bstack-sdk.d.ts +17 -0
  61. package/dist/tools/bstack-sdk.js +15 -8
  62. package/dist/tools/failurelogs-utils/app-automate.d.ts +7 -0
  63. package/dist/tools/failurelogs-utils/app-automate.js +29 -11
  64. package/dist/tools/failurelogs-utils/automate.d.ts +6 -0
  65. package/dist/tools/failurelogs-utils/automate.js +27 -12
  66. package/dist/tools/failurelogs-utils/utils.d.ts +30 -0
  67. package/dist/tools/getFailureLogs.d.ts +14 -0
  68. package/dist/tools/getFailureLogs.js +15 -16
  69. package/dist/tools/live-utils/desktop-filter.d.ts +2 -0
  70. package/dist/tools/live-utils/mobile-filter.d.ts +2 -0
  71. package/dist/tools/live-utils/start-session.d.ts +6 -0
  72. package/dist/tools/live-utils/start-session.js +18 -5
  73. package/dist/tools/live-utils/types.d.ts +33 -0
  74. package/dist/tools/live.d.ts +3 -0
  75. package/dist/tools/live.js +28 -16
  76. package/dist/tools/observability.d.ts +5 -0
  77. package/dist/tools/observability.js +14 -11
  78. package/dist/tools/sdk-utils/commands.d.ts +3 -0
  79. package/dist/tools/sdk-utils/commands.js +5 -4
  80. package/dist/tools/sdk-utils/constants.d.ts +2 -0
  81. package/dist/tools/sdk-utils/constants.js +82 -21
  82. package/dist/tools/sdk-utils/instructions.d.ts +6 -0
  83. package/dist/tools/sdk-utils/instructions.js +8 -6
  84. package/dist/tools/sdk-utils/percy/constants.d.ts +3 -0
  85. package/dist/tools/sdk-utils/percy/constants.js +1 -0
  86. package/dist/tools/sdk-utils/percy/instructions.d.ts +10 -0
  87. package/dist/tools/sdk-utils/percy/types.d.ts +5 -0
  88. package/dist/tools/sdk-utils/types.d.ts +39 -0
  89. package/dist/tools/sdk-utils/types.js +1 -0
  90. package/dist/tools/selfheal-utils/selfheal.d.ts +11 -0
  91. package/dist/tools/selfheal-utils/selfheal.js +10 -6
  92. package/dist/tools/selfheal.d.ts +7 -0
  93. package/dist/tools/selfheal.js +9 -7
  94. package/dist/tools/testmanagement-utils/TCG-utils/api.d.ts +34 -0
  95. package/dist/tools/testmanagement-utils/TCG-utils/api.js +57 -44
  96. package/dist/tools/testmanagement-utils/TCG-utils/config.d.ts +5 -0
  97. package/dist/tools/testmanagement-utils/TCG-utils/helpers.d.ts +13 -0
  98. package/dist/tools/testmanagement-utils/TCG-utils/helpers.js +2 -1
  99. package/dist/tools/testmanagement-utils/TCG-utils/types.d.ts +26 -0
  100. package/dist/tools/testmanagement-utils/add-test-result.d.ts +42 -0
  101. package/dist/tools/testmanagement-utils/add-test-result.js +23 -10
  102. package/dist/tools/testmanagement-utils/create-lca-steps.d.ts +100 -0
  103. package/dist/tools/testmanagement-utils/create-lca-steps.js +64 -55
  104. package/dist/tools/testmanagement-utils/create-project-folder.d.ts +31 -0
  105. package/dist/tools/testmanagement-utils/create-project-folder.js +31 -21
  106. package/dist/tools/testmanagement-utils/create-testcase.d.ts +122 -0
  107. package/dist/tools/testmanagement-utils/create-testcase.js +13 -10
  108. package/dist/tools/testmanagement-utils/create-testrun.d.ts +82 -0
  109. package/dist/tools/testmanagement-utils/create-testrun.js +11 -8
  110. package/dist/tools/testmanagement-utils/list-testcases.d.ts +30 -0
  111. package/dist/tools/testmanagement-utils/list-testcases.js +9 -7
  112. package/dist/tools/testmanagement-utils/list-testruns.d.ts +22 -0
  113. package/dist/tools/testmanagement-utils/list-testruns.js +9 -7
  114. package/dist/tools/testmanagement-utils/poll-lca-status.d.ts +11 -0
  115. package/dist/tools/testmanagement-utils/poll-lca-status.js +12 -8
  116. package/dist/tools/testmanagement-utils/testcase-from-file.d.ts +4 -0
  117. package/dist/tools/testmanagement-utils/testcase-from-file.js +6 -6
  118. package/dist/tools/testmanagement-utils/update-testrun.d.ts +40 -0
  119. package/dist/tools/testmanagement-utils/update-testrun.js +11 -7
  120. package/dist/tools/testmanagement-utils/upload-file.d.ts +20 -0
  121. package/dist/tools/testmanagement-utils/upload-file.js +8 -6
  122. package/dist/tools/testmanagement.d.ts +60 -0
  123. package/dist/tools/testmanagement.js +53 -53
  124. package/package.json +1 -1
package/README.md CHANGED
@@ -25,10 +25,13 @@
25
25
  <img src="assets/thumbnail.webp">
26
26
  </a>
27
27
  </div>
28
+
28
29
 
29
30
  Manage test cases, execute manual or automated tests, debug issues, and even fix code—directly within tools like Cursor, Claude, or any MCP-enabled client, using plain English.
30
31
  #### Test from anywhere:
31
32
  Easily connect the BrowserStack Test Platform to your favourite AI tools, such as IDEs, LLMs, or agentic workflows.
33
+ #### Test with natural language:
34
+ Manage, execute, debug tests, and even fix code using plain English prompts.
32
35
  #### Reduced context switching:
33
36
  Stay in flow—keep all project context in one place and trigger actions directly from your IDE or LLM.
34
37
 
@@ -115,7 +118,7 @@ Create and manage test cases, create test plans and trigger test runs using natu
115
118
  "update test results as passed for Login tests test run from My Demo Project"
116
119
  ```
117
120
 
118
- ### 🧪 Access BrowserStack AI agnets
121
+ ### 🧪 Access BrowserStack AI agents
119
122
 
120
123
  Generate test cases from PRDs, convert manual tests to low-code automation, and auto-heal flaky scripts powered by BrowserStack’s AI agents, seamlessly integrated into your workflow. Below are few example prompts to access Browserstack AI agents
121
124
 
@@ -0,0 +1,13 @@
1
+ /**
2
+ * USE_OWN_LOCAL_BINARY_PROCESS:
3
+ * If true, the system will not start a new local binary process, but will use the user's own process.
4
+ */
5
+ export declare class Config {
6
+ readonly DEV_MODE: boolean;
7
+ readonly browserstackLocalOptions: Record<string, any>;
8
+ readonly USE_OWN_LOCAL_BINARY_PROCESS: boolean;
9
+ readonly REMOTE_MCP: boolean;
10
+ constructor(DEV_MODE: boolean, browserstackLocalOptions: Record<string, any>, USE_OWN_LOCAL_BINARY_PROCESS: boolean, REMOTE_MCP: boolean);
11
+ }
12
+ declare const config: Config;
13
+ export default config;
package/dist/config.js CHANGED
@@ -33,18 +33,16 @@ for (const key of BROWSERSTACK_LOCAL_OPTION_KEYS) {
33
33
  * If true, the system will not start a new local binary process, but will use the user's own process.
34
34
  */
35
35
  export class Config {
36
- browserstackUsername;
37
- browserstackAccessKey;
38
36
  DEV_MODE;
39
37
  browserstackLocalOptions;
40
38
  USE_OWN_LOCAL_BINARY_PROCESS;
41
- constructor(browserstackUsername, browserstackAccessKey, DEV_MODE, browserstackLocalOptions, USE_OWN_LOCAL_BINARY_PROCESS) {
42
- this.browserstackUsername = browserstackUsername;
43
- this.browserstackAccessKey = browserstackAccessKey;
39
+ REMOTE_MCP;
40
+ constructor(DEV_MODE, browserstackLocalOptions, USE_OWN_LOCAL_BINARY_PROCESS, REMOTE_MCP) {
44
41
  this.DEV_MODE = DEV_MODE;
45
42
  this.browserstackLocalOptions = browserstackLocalOptions;
46
43
  this.USE_OWN_LOCAL_BINARY_PROCESS = USE_OWN_LOCAL_BINARY_PROCESS;
44
+ this.REMOTE_MCP = REMOTE_MCP;
47
45
  }
48
46
  }
49
- const config = new Config(process.env.BROWSERSTACK_USERNAME, process.env.BROWSERSTACK_ACCESS_KEY, process.env.DEV_MODE === "true", browserstackLocalOptions, process.env.USE_OWN_LOCAL_BINARY_PROCESS === "true");
47
+ const config = new Config(process.env.DEV_MODE === "true", browserstackLocalOptions, process.env.USE_OWN_LOCAL_BINARY_PROCESS === "true", process.env.REMOTE_MCP === "true");
50
48
  export default config;
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import "dotenv/config";
3
+ export { setLogger } from "./logger.js";
4
+ export { BrowserStackMcpServer } from "./server-factory.js";
package/dist/index.js CHANGED
@@ -1,47 +1,37 @@
1
1
  #!/usr/bin/env node
2
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
2
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
3
  import { createRequire } from "module";
5
4
  const require = createRequire(import.meta.url);
6
5
  const packageJson = require("../package.json");
7
6
  import "dotenv/config";
8
7
  import logger from "./logger.js";
9
- import addSDKTools from "./tools/bstack-sdk.js";
10
- import addAppLiveTools from "./tools/applive.js";
11
- import addBrowserLiveTools from "./tools/live.js";
12
- import addAccessibilityTools from "./tools/accessibility.js";
13
- import addTestManagementTools from "./tools/testmanagement.js";
14
- import addAppAutomationTools from "./tools/appautomate.js";
15
- import addFailureLogsTools from "./tools/getFailureLogs.js";
16
- import addAutomateTools from "./tools/automate.js";
17
- import addSelfHealTools from "./tools/selfheal.js";
18
- import { setupOnInitialized } from "./oninitialized.js";
19
- function registerTools(server) {
20
- addAccessibilityTools(server);
21
- addSDKTools(server);
22
- addAppLiveTools(server);
23
- addBrowserLiveTools(server);
24
- addTestManagementTools(server);
25
- addAppAutomationTools(server);
26
- addFailureLogsTools(server);
27
- addAutomateTools(server);
28
- addSelfHealTools(server);
29
- }
30
- // Create an MCP server
31
- const server = new McpServer({
32
- name: "BrowserStack MCP Server",
33
- version: packageJson.version,
34
- });
35
- setupOnInitialized(server);
36
- registerTools(server);
8
+ import { BrowserStackMcpServer } from "./server-factory.js";
37
9
  async function main() {
38
10
  logger.info("Launching BrowserStack MCP server, version %s", packageJson.version);
39
- // Start receiving messages on stdin and sending messages on stdout
11
+ const remoteMCP = process.env.REMOTE_MCP === "true";
12
+ if (remoteMCP) {
13
+ logger.info("Running in remote MCP mode");
14
+ return;
15
+ }
16
+ const username = process.env.BROWSERSTACK_USERNAME;
17
+ const accessKey = process.env.BROWSERSTACK_ACCESS_KEY;
18
+ if (!username) {
19
+ throw new Error("BROWSERSTACK_USERNAME environment variable is required");
20
+ }
21
+ if (!accessKey) {
22
+ throw new Error("BROWSERSTACK_ACCESS_KEY environment variable is required");
23
+ }
40
24
  const transport = new StdioServerTransport();
41
- await server.connect(transport);
25
+ const mcpServer = new BrowserStackMcpServer({
26
+ "browserstack-username": username,
27
+ "browserstack-access-key": accessKey,
28
+ });
29
+ await mcpServer.getInstance().connect(transport);
42
30
  }
43
31
  main().catch(console.error);
44
32
  // Ensure logs are flushed before exit
45
33
  process.on("exit", () => {
46
34
  logger.flush();
47
35
  });
36
+ export { setLogger } from "./logger.js";
37
+ export { BrowserStackMcpServer } from "./server-factory.js";
@@ -0,0 +1,2 @@
1
+ import { BrowserStackConfig } from "../lib/types.js";
2
+ export declare function getLatestO11YBuildInfo(buildName: string, projectName: string, config: BrowserStackConfig): Promise<import("./apiClient.js").ApiResponse<any>>;
package/dist/lib/api.js CHANGED
@@ -1,10 +1,15 @@
1
- import config from "../config.js";
2
- export async function getLatestO11YBuildInfo(buildName, projectName) {
1
+ import { getBrowserStackAuth } from "./get-auth.js";
2
+ import { apiClient } from "./apiClient.js";
3
+ export async function getLatestO11YBuildInfo(buildName, projectName, config) {
3
4
  const buildsUrl = `https://api-observability.browserstack.com/ext/v1/builds/latest?build_name=${encodeURIComponent(buildName)}&project_name=${encodeURIComponent(projectName)}`;
4
- const buildsResponse = await fetch(buildsUrl, {
5
+ const authString = getBrowserStackAuth(config);
6
+ const auth = Buffer.from(authString).toString("base64");
7
+ const buildsResponse = await apiClient.get({
8
+ url: buildsUrl,
5
9
  headers: {
6
- Authorization: `Basic ${Buffer.from(`${config.browserstackUsername}:${config.browserstackAccessKey}`).toString("base64")}`,
10
+ Authorization: `Basic ${auth}`,
7
11
  },
12
+ raise_error: false,
8
13
  });
9
14
  if (!buildsResponse.ok) {
10
15
  if (buildsResponse.statusText === "Unauthorized") {
@@ -12,5 +17,5 @@ export async function getLatestO11YBuildInfo(buildName, projectName) {
12
17
  }
13
18
  throw new Error(`Failed to fetch builds: ${buildsResponse.statusText}`);
14
19
  }
15
- return buildsResponse.json();
20
+ return buildsResponse;
16
21
  }
@@ -0,0 +1,31 @@
1
+ import { AxiosRequestConfig, AxiosResponse } from "axios";
2
+ type RequestOptions = {
3
+ url: string;
4
+ headers?: Record<string, string>;
5
+ params?: Record<string, string | number>;
6
+ body?: any;
7
+ raise_error?: boolean;
8
+ };
9
+ declare class ApiResponse<T = any> {
10
+ private _response;
11
+ constructor(response: AxiosResponse<T>);
12
+ get data(): T;
13
+ get status(): number;
14
+ get statusText(): string;
15
+ get headers(): Record<string, string>;
16
+ get config(): AxiosRequestConfig;
17
+ get url(): string | undefined;
18
+ get ok(): boolean;
19
+ }
20
+ declare class ApiClient {
21
+ private instance;
22
+ private get axiosAgent();
23
+ private requestWrapper;
24
+ get<T = any>({ url, headers, params, raise_error, }: RequestOptions): Promise<ApiResponse<T>>;
25
+ post<T = any>({ url, headers, body, raise_error, }: RequestOptions): Promise<ApiResponse<T>>;
26
+ put<T = any>({ url, headers, body, raise_error, }: RequestOptions): Promise<ApiResponse<T>>;
27
+ patch<T = any>({ url, headers, body, raise_error, }: RequestOptions): Promise<ApiResponse<T>>;
28
+ delete<T = any>({ url, headers, params, raise_error, }: RequestOptions): Promise<ApiResponse<T>>;
29
+ }
30
+ export declare const apiClient: ApiClient;
31
+ export type { ApiResponse, RequestOptions };
@@ -0,0 +1,108 @@
1
+ import axios from "axios";
2
+ import httpsProxyAgentPkg from "https-proxy-agent";
3
+ const { HttpsProxyAgent } = httpsProxyAgentPkg;
4
+ import * as https from "https";
5
+ import * as fs from "fs";
6
+ import config from "../config.js";
7
+ class ApiResponse {
8
+ _response;
9
+ constructor(response) {
10
+ this._response = response;
11
+ }
12
+ get data() {
13
+ return this._response.data;
14
+ }
15
+ get status() {
16
+ return this._response.status;
17
+ }
18
+ get statusText() {
19
+ return this._response.statusText;
20
+ }
21
+ get headers() {
22
+ const raw = this._response.headers;
23
+ const sanitized = {};
24
+ for (const key in raw) {
25
+ const value = raw[key];
26
+ if (typeof value === "string") {
27
+ sanitized[key] = value;
28
+ }
29
+ }
30
+ return sanitized;
31
+ }
32
+ get config() {
33
+ return this._response.config;
34
+ }
35
+ get url() {
36
+ return this._response.config.url;
37
+ }
38
+ get ok() {
39
+ return this._response.status >= 200 && this._response.status < 300;
40
+ }
41
+ }
42
+ // Utility to create HTTPS agent if needed (proxy/CA)
43
+ function getAxiosAgent() {
44
+ const proxyHost = config.browserstackLocalOptions.proxyHost;
45
+ const proxyPort = config.browserstackLocalOptions.proxyPort;
46
+ const caCertPath = config.browserstackLocalOptions.useCaCertificate;
47
+ // If both proxy host and port are defined
48
+ if (proxyHost && proxyPort) {
49
+ const proxyUrl = `http://${proxyHost}:${proxyPort}`;
50
+ if (caCertPath && fs.existsSync(caCertPath)) {
51
+ // Proxy + CA cert
52
+ const ca = fs.readFileSync(caCertPath);
53
+ return new HttpsProxyAgent({
54
+ host: proxyHost,
55
+ port: Number(proxyPort),
56
+ ca,
57
+ rejectUnauthorized: false, // Set to true if you want strict SSL
58
+ });
59
+ }
60
+ else {
61
+ // Proxy only
62
+ return new HttpsProxyAgent(proxyUrl);
63
+ }
64
+ }
65
+ else if (caCertPath && fs.existsSync(caCertPath)) {
66
+ // CA only
67
+ return new https.Agent({
68
+ ca: fs.readFileSync(caCertPath),
69
+ rejectUnauthorized: false, // Set to true for strict SSL
70
+ });
71
+ }
72
+ // Default agent (no proxy, no CA)
73
+ return undefined;
74
+ }
75
+ class ApiClient {
76
+ instance = axios.create();
77
+ get axiosAgent() {
78
+ return getAxiosAgent();
79
+ }
80
+ async requestWrapper(fn, raise_error = true) {
81
+ try {
82
+ const res = await fn(this.axiosAgent);
83
+ return new ApiResponse(res);
84
+ }
85
+ catch (error) {
86
+ if (error.response && !raise_error) {
87
+ return new ApiResponse(error.response);
88
+ }
89
+ throw error;
90
+ }
91
+ }
92
+ async get({ url, headers, params, raise_error = true, }) {
93
+ return this.requestWrapper((agent) => this.instance.get(url, { headers, params, httpsAgent: agent }), raise_error);
94
+ }
95
+ async post({ url, headers, body, raise_error = true, }) {
96
+ return this.requestWrapper((agent) => this.instance.post(url, body, { headers, httpsAgent: agent }), raise_error);
97
+ }
98
+ async put({ url, headers, body, raise_error = true, }) {
99
+ return this.requestWrapper((agent) => this.instance.put(url, body, { headers, httpsAgent: agent }), raise_error);
100
+ }
101
+ async patch({ url, headers, body, raise_error = true, }) {
102
+ return this.requestWrapper((agent) => this.instance.patch(url, body, { headers, httpsAgent: agent }), raise_error);
103
+ }
104
+ async delete({ url, headers, params, raise_error = true, }) {
105
+ return this.requestWrapper((agent) => this.instance.delete(url, { headers, params, httpsAgent: agent }), raise_error);
106
+ }
107
+ }
108
+ export const apiClient = new ApiClient();
@@ -0,0 +1,17 @@
1
+ export declare const SessionType: {
2
+ readonly Automate: "automate";
3
+ readonly AppAutomate: "app-automate";
4
+ };
5
+ export declare const AutomateLogType: {
6
+ readonly NetworkLogs: "networkLogs";
7
+ readonly SessionLogs: "sessionLogs";
8
+ readonly ConsoleLogs: "consoleLogs";
9
+ };
10
+ export declare const AppAutomateLogType: {
11
+ readonly DeviceLogs: "deviceLogs";
12
+ readonly AppiumLogs: "appiumLogs";
13
+ readonly CrashLogs: "crashLogs";
14
+ };
15
+ export type SessionType = (typeof SessionType)[keyof typeof SessionType];
16
+ export type AutomateLogType = (typeof AutomateLogType)[keyof typeof AutomateLogType];
17
+ export type AppAutomateLogType = (typeof AppAutomateLogType)[keyof typeof AppAutomateLogType];
@@ -0,0 +1,9 @@
1
+ export declare enum BrowserStackProducts {
2
+ LIVE = "live",
3
+ APP_LIVE = "app_live",
4
+ APP_AUTOMATE = "app_automate"
5
+ }
6
+ /**
7
+ * Fetches and caches BrowserStack datasets (live + app_live + app_automate) with a shared TTL.
8
+ */
9
+ export declare function getDevicesAndBrowsers(type: BrowserStackProducts): Promise<any>;
@@ -1,6 +1,7 @@
1
1
  import fs from "fs";
2
2
  import os from "os";
3
3
  import path from "path";
4
+ import { apiClient } from "./apiClient.js";
4
5
  const CACHE_DIR = path.join(os.homedir(), ".browserstack", "combined_cache");
5
6
  const CACHE_FILE = path.join(CACHE_DIR, "data.json");
6
7
  const TTL_MS = 24 * 60 * 60 * 1000; // 1 day
@@ -38,13 +39,12 @@ export async function getDevicesAndBrowsers(type) {
38
39
  }
39
40
  }
40
41
  }
41
- const liveRes = await fetch(URLS[type]);
42
+ const liveRes = await apiClient.get({ url: URLS[type], raise_error: false });
42
43
  if (!liveRes.ok) {
43
44
  throw new Error(`Failed to fetch configuration from BrowserStack : ${type}=${liveRes.statusText}`);
44
45
  }
45
- const data = await liveRes.json();
46
46
  cache = {
47
- [type]: data,
47
+ [type]: liveRes.data,
48
48
  };
49
49
  fs.writeFileSync(CACHE_FILE, JSON.stringify(cache), "utf8");
50
50
  return cache[type];
@@ -0,0 +1,7 @@
1
+ import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
2
+ /**
3
+ * Formats an AxiosError into a CallToolResult with an appropriate message.
4
+ * @param err - The error object to format
5
+ * @param defaultText - The fallback error message
6
+ */
7
+ export declare function formatAxiosError(err: unknown, defaultText: string): CallToolResult;
@@ -0,0 +1 @@
1
+ export declare function customFuzzySearch<T>(list: T[], keys: Array<keyof T | string>, query: string, limit?: number, maxDistance?: number): T[];
@@ -0,0 +1,2 @@
1
+ import { BrowserStackConfig } from "../lib/types.js";
2
+ export declare function getBrowserStackAuth(config: BrowserStackConfig): string;
@@ -0,0 +1,8 @@
1
+ export function getBrowserStackAuth(config) {
2
+ const username = config["browserstack-username"];
3
+ const accessKey = config["browserstack-access-key"];
4
+ if (!username || !accessKey) {
5
+ throw new Error("BrowserStack credentials not set on server.authHeaders");
6
+ }
7
+ return `${username}:${accessKey}`;
8
+ }
@@ -0,0 +1 @@
1
+ export declare const signedUrlMap: Map<string, object>;
@@ -0,0 +1,4 @@
1
+ export declare function trackMCP(toolName: string, clientInfo: {
2
+ name?: string;
3
+ version?: string;
4
+ }, error?: unknown, config?: any): void;
@@ -1,14 +1,10 @@
1
1
  import logger from "../logger.js";
2
- import config from "../config.js";
2
+ import { getBrowserStackAuth } from "./get-auth.js";
3
3
  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
- export function trackMCP(toolName, clientInfo, error) {
8
- if (config.DEV_MODE) {
9
- logger.info("Tracking MCP is disabled in dev mode");
10
- return;
11
- }
7
+ export function trackMCP(toolName, clientInfo, error, config) {
12
8
  const instrumentationEndpoint = "https://api.browserstack.com/sdk/v1/event";
13
9
  const isSuccess = !error;
14
10
  const mcpClient = clientInfo?.name || "unknown";
@@ -35,11 +31,16 @@ export function trackMCP(toolName, clientInfo, error) {
35
31
  event.event_properties.error_type =
36
32
  error instanceof Error ? error.constructor.name : "Unknown";
37
33
  }
34
+ let authHeader = undefined;
35
+ if (config) {
36
+ const authString = getBrowserStackAuth(config);
37
+ authHeader = `Basic ${Buffer.from(authString).toString("base64")}`;
38
+ }
38
39
  axios
39
40
  .post(instrumentationEndpoint, event, {
40
41
  headers: {
41
42
  "Content-Type": "application/json",
42
- Authorization: `Basic ${Buffer.from(`${config.browserstackUsername}:${config.browserstackAccessKey}`).toString("base64")}`,
43
+ ...(authHeader ? { Authorization: authHeader } : {}),
43
44
  },
44
45
  timeout: 2000,
45
46
  })
@@ -0,0 +1,3 @@
1
+ export declare function killExistingBrowserStackLocalProcesses(): Promise<void>;
2
+ export declare function ensureLocalBinarySetup(username: string, password: string, localIdentifier?: string): Promise<void>;
3
+ export declare function isLocalURL(url: string): boolean;
package/dist/lib/local.js CHANGED
@@ -65,7 +65,7 @@ export async function killExistingBrowserStackLocalProcesses() {
65
65
  // Continue execution as there may not be any processes running
66
66
  }
67
67
  }
68
- export async function ensureLocalBinarySetup(localIdentifier) {
68
+ export async function ensureLocalBinarySetup(username, password, localIdentifier) {
69
69
  logger.info("Ensuring local binary setup as it is required for private URLs...");
70
70
  if (config.USE_OWN_LOCAL_BINARY_PROCESS) {
71
71
  logger.info("Using user's own BrowserStack Local binary process, checking if it's running...");
@@ -81,8 +81,8 @@ export async function ensureLocalBinarySetup(localIdentifier) {
81
81
  // Use a single options object from config and extend with required fields
82
82
  const bsLocalArgs = {
83
83
  ...(config.browserstackLocalOptions || {}),
84
- key: config.browserstackAccessKey,
85
- username: config.browserstackUsername,
84
+ key: password,
85
+ username,
86
86
  };
87
87
  if (localIdentifier) {
88
88
  bsLocalArgs.localIdentifier = localIdentifier;
@@ -0,0 +1,4 @@
1
+ export type BrowserStackConfig = {
2
+ "browserstack-username": string;
3
+ "browserstack-access-key": string;
4
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,4 @@
1
+ import type { ApiResponse } from "./apiClient.js";
2
+ export declare function sanitizeUrlParam(param: string): string;
3
+ export declare function maybeCompressBase64(base64: string): Promise<string>;
4
+ export declare function assertOkResponse(response: Response | ApiResponse, action: string): Promise<void>;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * If req === "latest" or "oldest", returns max/min numeric (or lex)
3
+ * Else if exact match, returns that
4
+ * Else picks the numerically closest (or first)
5
+ */
6
+ export declare function resolveVersion(requested: string, available: string[]): string;
@@ -0,0 +1,3 @@
1
+ declare const logger: any;
2
+ export declare function setLogger(customLogger: any): void;
3
+ export default logger;
package/dist/logger.js CHANGED
@@ -1,7 +1,8 @@
1
1
  import { pino } from "pino";
2
- let logger;
2
+ // 1. The actual logger instance, swapped out as needed
3
+ let currentLogger;
3
4
  if (process.env.NODE_ENV === "development") {
4
- logger = pino({
5
+ currentLogger = pino({
5
6
  level: "debug",
6
7
  transport: {
7
8
  targets: [
@@ -21,8 +22,8 @@ if (process.env.NODE_ENV === "development") {
21
22
  });
22
23
  }
23
24
  else {
24
- // NULL logger
25
- logger = pino({
25
+ // Null logger (logs go to /dev/null or NUL)
26
+ currentLogger = pino({
26
27
  level: "info",
27
28
  transport: {
28
29
  target: "pino/file",
@@ -32,4 +33,19 @@ else {
32
33
  },
33
34
  });
34
35
  }
36
+ // 2. Proxy logger: always delegates to the currentLogger
37
+ const logger = new Proxy({}, {
38
+ get(_target, prop) {
39
+ // Forward function calls to currentLogger
40
+ if (typeof currentLogger[prop] === "function") {
41
+ return (...args) => currentLogger[prop](...args);
42
+ }
43
+ // Forward property gets
44
+ return currentLogger[prop];
45
+ },
46
+ });
47
+ // 3. Setter to update the logger instance everywhere
48
+ export function setLogger(customLogger) {
49
+ currentLogger = customLogger;
50
+ }
35
51
  export default logger;
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function setupOnInitialized(server: McpServer, config?: any): void;
@@ -1,16 +1,11 @@
1
- import config from "./config.js";
2
1
  import { trackMCP } from "./lib/instrumentation.js";
3
- export function setupOnInitialized(server) {
2
+ export function setupOnInitialized(server, config) {
4
3
  const nodeVersion = process.versions.node;
5
4
  // Check for Node.js version
6
5
  if (nodeVersion < "18.0.0") {
7
6
  throw new Error("Node version is not supported. Please upgrade to 18.0.0 or later.");
8
7
  }
9
- // Check for BrowserStack credentials
10
- if (!config.browserstackUsername || !config.browserstackAccessKey) {
11
- throw new Error("BrowserStack credentials are missing. Please provide a valid username and access key.");
12
- }
13
8
  server.server.oninitialized = () => {
14
- trackMCP("started", server.server.getClientVersion());
9
+ trackMCP("started", server.server.getClientVersion(), undefined, config);
15
10
  };
16
11
  }
@@ -0,0 +1,25 @@
1
+ import { McpServer, RegisteredTool } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { BrowserStackConfig } from "./lib/types.js";
3
+ /**
4
+ * Wrapper class for BrowserStack MCP Server
5
+ * Stores a map of registered tools by name
6
+ */
7
+ export declare class BrowserStackMcpServer {
8
+ private config;
9
+ server: McpServer;
10
+ tools: Record<string, RegisteredTool>;
11
+ constructor(config: BrowserStackConfig);
12
+ /**
13
+ * Calls each tool-adder function and collects their returned tools
14
+ */
15
+ private registerTools;
16
+ /**
17
+ * Expose the underlying MCP server instance
18
+ */
19
+ getInstance(): McpServer;
20
+ /**
21
+ * Get all registered tools
22
+ */
23
+ getTools(): Record<string, RegisteredTool>;
24
+ getTool(name: string): RegisteredTool | undefined;
25
+ }
@@ -0,0 +1,70 @@
1
+ import { McpServer, } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { createRequire } from "module";
3
+ const require = createRequire(import.meta.url);
4
+ const packageJson = require("../package.json");
5
+ import logger from "./logger.js";
6
+ import addSDKTools from "./tools/bstack-sdk.js";
7
+ import addBrowserLiveTools from "./tools/live.js";
8
+ import addAccessibilityTools from "./tools/accessibility.js";
9
+ import addTestManagementTools from "./tools/testmanagement.js";
10
+ import addAppAutomationTools from "./tools/appautomate.js";
11
+ import addFailureLogsTools from "./tools/getFailureLogs.js";
12
+ import addAutomateTools from "./tools/automate.js";
13
+ import addSelfHealTools from "./tools/selfheal.js";
14
+ import addAppLiveTools from "./tools/applive.js";
15
+ import { setupOnInitialized } from "./oninitialized.js";
16
+ /**
17
+ * Wrapper class for BrowserStack MCP Server
18
+ * Stores a map of registered tools by name
19
+ */
20
+ export class BrowserStackMcpServer {
21
+ config;
22
+ server;
23
+ tools = {};
24
+ constructor(config) {
25
+ this.config = config;
26
+ logger.info("Creating BrowserStack MCP Server, version %s", packageJson.version);
27
+ this.server = new McpServer({
28
+ name: "BrowserStack MCP Server",
29
+ version: packageJson.version,
30
+ });
31
+ setupOnInitialized(this.server, this.config);
32
+ this.registerTools();
33
+ }
34
+ /**
35
+ * Calls each tool-adder function and collects their returned tools
36
+ */
37
+ registerTools() {
38
+ const toolAdders = [
39
+ addAccessibilityTools,
40
+ addSDKTools,
41
+ addAppLiveTools,
42
+ addBrowserLiveTools,
43
+ addTestManagementTools,
44
+ addAppAutomationTools,
45
+ addFailureLogsTools,
46
+ addAutomateTools,
47
+ addSelfHealTools,
48
+ ];
49
+ toolAdders.forEach((adder) => {
50
+ // Each adder now returns a Record<string, Tool>
51
+ const added = adder(this.server, this.config);
52
+ Object.assign(this.tools, added);
53
+ });
54
+ }
55
+ /**
56
+ * Expose the underlying MCP server instance
57
+ */
58
+ getInstance() {
59
+ return this.server;
60
+ }
61
+ /**
62
+ * Get all registered tools
63
+ */
64
+ getTools() {
65
+ return this.tools;
66
+ }
67
+ getTool(name) {
68
+ return this.tools[name];
69
+ }
70
+ }
@@ -0,0 +1,3 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { BrowserStackConfig } from "../lib/types.js";
3
+ export default function addAccessibilityTools(server: McpServer, config: BrowserStackConfig): Record<string, any>;