@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
@@ -1,20 +1,23 @@
1
- import config from "../../config.js";
1
+ import { getBrowserStackAuth } from "../../lib/get-auth.js";
2
2
  import { filterLinesByKeywords, validateLogResponse, } from "./utils.js";
3
- const auth = Buffer.from(`${config.browserstackUsername}:${config.browserstackAccessKey}`).toString("base64");
3
+ import { apiClient } from "../../lib/apiClient.js";
4
4
  // NETWORK LOGS
5
- export async function retrieveNetworkFailures(sessionId) {
5
+ export async function retrieveNetworkFailures(sessionId, config) {
6
6
  const url = `https://api.browserstack.com/automate/sessions/${sessionId}/networklogs`;
7
- const response = await fetch(url, {
8
- method: "GET",
7
+ const authString = getBrowserStackAuth(config);
8
+ const auth = Buffer.from(authString).toString("base64");
9
+ const response = await apiClient.get({
10
+ url,
9
11
  headers: {
10
12
  "Content-Type": "application/json",
11
13
  Authorization: `Basic ${auth}`,
12
14
  },
15
+ raise_error: false,
13
16
  });
14
17
  const validationError = validateLogResponse(response, "network logs");
15
18
  if (validationError)
16
19
  return validationError.message;
17
- const networklogs = await response.json();
20
+ const networklogs = response.data;
18
21
  const failureEntries = networklogs.log.entries.filter((entry) => entry.response.status === 0 ||
19
22
  entry.response.status >= 400 ||
20
23
  entry.response._error !== undefined);
@@ -37,36 +40,48 @@ export async function retrieveNetworkFailures(sessionId) {
37
40
  : "No network failures found";
38
41
  }
39
42
  // SESSION LOGS
40
- export async function retrieveSessionFailures(sessionId) {
43
+ export async function retrieveSessionFailures(sessionId, config) {
41
44
  const url = `https://api.browserstack.com/automate/sessions/${sessionId}/logs`;
42
- const response = await fetch(url, {
45
+ const authString = getBrowserStackAuth(config);
46
+ const auth = Buffer.from(authString).toString("base64");
47
+ const response = await apiClient.get({
48
+ url,
43
49
  headers: {
44
50
  "Content-Type": "application/json",
45
51
  Authorization: `Basic ${auth}`,
46
52
  },
53
+ raise_error: false,
47
54
  });
48
55
  const validationError = validateLogResponse(response, "session logs");
49
56
  if (validationError)
50
57
  return validationError.message;
51
- const logText = await response.text();
58
+ const logText = typeof response.data === "string"
59
+ ? response.data
60
+ : JSON.stringify(response.data);
52
61
  const logs = filterSessionFailures(logText);
53
62
  return logs.length > 0
54
63
  ? `Session Failures (${logs.length} found):\n${JSON.stringify(logs, null, 2)}`
55
64
  : "No session failures found";
56
65
  }
57
66
  // CONSOLE LOGS
58
- export async function retrieveConsoleFailures(sessionId) {
67
+ export async function retrieveConsoleFailures(sessionId, config) {
59
68
  const url = `https://api.browserstack.com/automate/sessions/${sessionId}/consolelogs`;
60
- const response = await fetch(url, {
69
+ const authString = getBrowserStackAuth(config);
70
+ const auth = Buffer.from(authString).toString("base64");
71
+ const response = await apiClient.get({
72
+ url,
61
73
  headers: {
62
74
  "Content-Type": "application/json",
63
75
  Authorization: `Basic ${auth}`,
64
76
  },
77
+ raise_error: false,
65
78
  });
66
79
  const validationError = validateLogResponse(response, "console logs");
67
80
  if (validationError)
68
81
  return validationError.message;
69
- const logText = await response.text();
82
+ const logText = typeof response.data === "string"
83
+ ? response.data
84
+ : JSON.stringify(response.data);
70
85
  const logs = filterConsoleFailures(logText);
71
86
  return logs.length > 0
72
87
  ? `Console Failures (${logs.length} found):\n${JSON.stringify(logs, null, 2)}`
@@ -0,0 +1,30 @@
1
+ import type { ApiResponse } from "../../lib/apiClient.js";
2
+ export interface LogResponse {
3
+ logs?: any[];
4
+ message?: string;
5
+ }
6
+ export interface HarFile {
7
+ log: {
8
+ entries: HarEntry[];
9
+ };
10
+ }
11
+ export interface HarEntry {
12
+ startedDateTime: string;
13
+ request: {
14
+ method: string;
15
+ url: string;
16
+ queryString?: {
17
+ name: string;
18
+ value: string;
19
+ }[];
20
+ };
21
+ response: {
22
+ status: number;
23
+ statusText?: string;
24
+ _error?: string;
25
+ };
26
+ serverIPAddress?: string;
27
+ time?: number;
28
+ }
29
+ export declare function validateLogResponse(response: Response | ApiResponse, logType: string): LogResponse | null;
30
+ export declare function filterLinesByKeywords(logText: string, keywords: string[]): string[];
@@ -0,0 +1,14 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
3
+ import { BrowserStackConfig } from "../lib/types.js";
4
+ import { AppAutomateLogType, AutomateLogType, SessionType } from "../lib/constants.js";
5
+ type LogType = AutomateLogType | AppAutomateLogType;
6
+ type SessionTypeValues = SessionType;
7
+ export declare function getFailureLogs(args: {
8
+ sessionId: string;
9
+ buildId?: string;
10
+ logTypes: LogType[];
11
+ sessionType: SessionTypeValues;
12
+ }, config: BrowserStackConfig): Promise<CallToolResult>;
13
+ export default function registerGetFailureLogs(server: McpServer, config: BrowserStackConfig): Record<string, any>;
14
+ export {};
@@ -1,11 +1,10 @@
1
1
  import { z } from "zod";
2
- import logger from "../logger.js";
3
2
  import { trackMCP } from "../lib/instrumentation.js";
4
3
  import { retrieveNetworkFailures, retrieveSessionFailures, retrieveConsoleFailures, } from "./failurelogs-utils/automate.js";
5
4
  import { retrieveDeviceLogs, retrieveAppiumLogs, retrieveCrashLogs, } from "./failurelogs-utils/app-automate.js";
6
5
  import { AppAutomateLogType, AutomateLogType, SessionType, } from "../lib/constants.js";
7
6
  // Main log fetcher function
8
- export async function getFailureLogs(args) {
7
+ export async function getFailureLogs(args, config) {
9
8
  const results = [];
10
9
  const errors = [];
11
10
  let validLogTypes = [];
@@ -53,32 +52,32 @@ export async function getFailureLogs(args) {
53
52
  for (const logType of validLogTypes) {
54
53
  switch (logType) {
55
54
  case AutomateLogType.NetworkLogs: {
56
- response = await retrieveNetworkFailures(args.sessionId);
55
+ response = await retrieveNetworkFailures(args.sessionId, config);
57
56
  results.push({ type: "text", text: response });
58
57
  break;
59
58
  }
60
59
  case AutomateLogType.SessionLogs: {
61
- response = await retrieveSessionFailures(args.sessionId);
60
+ response = await retrieveSessionFailures(args.sessionId, config);
62
61
  results.push({ type: "text", text: response });
63
62
  break;
64
63
  }
65
64
  case AutomateLogType.ConsoleLogs: {
66
- response = await retrieveConsoleFailures(args.sessionId);
65
+ response = await retrieveConsoleFailures(args.sessionId, config);
67
66
  results.push({ type: "text", text: response });
68
67
  break;
69
68
  }
70
69
  case AppAutomateLogType.DeviceLogs: {
71
- response = await retrieveDeviceLogs(args.sessionId, args.buildId);
70
+ response = await retrieveDeviceLogs(args.sessionId, args.buildId, config);
72
71
  results.push({ type: "text", text: response });
73
72
  break;
74
73
  }
75
74
  case AppAutomateLogType.AppiumLogs: {
76
- response = await retrieveAppiumLogs(args.sessionId, args.buildId);
75
+ response = await retrieveAppiumLogs(args.sessionId, args.buildId, config);
77
76
  results.push({ type: "text", text: response });
78
77
  break;
79
78
  }
80
79
  case AppAutomateLogType.CrashLogs: {
81
- response = await retrieveCrashLogs(args.sessionId, args.buildId);
80
+ response = await retrieveCrashLogs(args.sessionId, args.buildId, config);
82
81
  results.push({ type: "text", text: response });
83
82
  break;
84
83
  }
@@ -98,8 +97,9 @@ export async function getFailureLogs(args) {
98
97
  return { content: results };
99
98
  }
100
99
  // Register tool with the MCP server
101
- export default function registerGetFailureLogs(server) {
102
- server.tool("getFailureLogs", "Fetch various types of logs from a BrowserStack session. Supports both automate and app-automate sessions.", {
100
+ export default function registerGetFailureLogs(server, config) {
101
+ const tools = {};
102
+ tools.getFailureLogs = server.tool("getFailureLogs", "Fetch various types of logs from a BrowserStack session. Supports both automate and app-automate sessions.", {
103
103
  sessionType: z
104
104
  .enum([SessionType.Automate, SessionType.AppAutomate])
105
105
  .describe("Type of BrowserStack session. Must be explicitly provided by the user."),
@@ -122,18 +122,16 @@ export default function registerGetFailureLogs(server) {
122
122
  .describe("The types of logs to fetch."),
123
123
  }, async (args) => {
124
124
  try {
125
- trackMCP("getFailureLogs", server.server.getClientVersion());
126
- return await getFailureLogs(args);
125
+ trackMCP("getFailureLogs", server.server.getClientVersion(), undefined, config);
126
+ return await getFailureLogs(args, config);
127
127
  }
128
128
  catch (error) {
129
- const message = error instanceof Error ? error.message : String(error);
130
- trackMCP("getFailureLogs", server.server.getClientVersion(), error);
131
- logger.error("Failed to fetch logs: %s", message);
129
+ trackMCP("getFailureLogs", server.server.getClientVersion(), error, config);
132
130
  return {
133
131
  content: [
134
132
  {
135
133
  type: "text",
136
- text: `Failed to fetch logs: ${message}`,
134
+ text: `Failed to fetch failure logs: ${error instanceof Error ? error.message : "Unknown error"}`,
137
135
  isError: true,
138
136
  },
139
137
  ],
@@ -141,4 +139,5 @@ export default function registerGetFailureLogs(server) {
141
139
  };
142
140
  }
143
141
  });
142
+ return tools;
144
143
  }
@@ -0,0 +1,2 @@
1
+ import { DesktopSearchArgs, DesktopEntry } from "./types.js";
2
+ export declare function filterDesktop(args: DesktopSearchArgs): Promise<DesktopEntry>;
@@ -0,0 +1,2 @@
1
+ import { MobileSearchArgs, MobileEntry } from "./types.js";
2
+ export declare function filterMobile(args: MobileSearchArgs): Promise<MobileEntry>;
@@ -0,0 +1,6 @@
1
+ import { DesktopSearchArgs, MobileSearchArgs } from "./types.js";
2
+ import { BrowserStackConfig } from "../../lib/types.js";
3
+ /**
4
+ * Entrypoint: detects platformType & delegates.
5
+ */
6
+ export declare function startBrowserSession(args: DesktopSearchArgs | MobileSearchArgs, config: BrowserStackConfig): Promise<string>;
@@ -4,13 +4,18 @@ import { filterDesktop } from "./desktop-filter.js";
4
4
  import { filterMobile } from "./mobile-filter.js";
5
5
  import { PlatformType, } from "./types.js";
6
6
  import { isLocalURL, ensureLocalBinarySetup, killExistingBrowserStackLocalProcesses, } from "../../lib/local.js";
7
+ import { getBrowserStackAuth } from "../../lib/get-auth.js";
8
+ import envConfig from "../../config.js";
7
9
  /**
8
10
  * Prepares local tunnel setup based on URL type
9
11
  */
10
- async function prepareLocalTunnel(url) {
12
+ async function prepareLocalTunnel(url, username, password) {
11
13
  const isLocal = isLocalURL(url);
14
+ if (isLocal && envConfig.REMOTE_MCP) {
15
+ throw new Error("Local URLs are not supported in this remote mcp. Please use a public URL.");
16
+ }
12
17
  if (isLocal) {
13
- await ensureLocalBinarySetup();
18
+ await ensureLocalBinarySetup(username, password);
14
19
  }
15
20
  else {
16
21
  await killExistingBrowserStackLocalProcesses();
@@ -20,15 +25,23 @@ async function prepareLocalTunnel(url) {
20
25
  /**
21
26
  * Entrypoint: detects platformType & delegates.
22
27
  */
23
- export async function startBrowserSession(args) {
28
+ export async function startBrowserSession(args, config) {
24
29
  const entry = args.platformType === PlatformType.DESKTOP
25
30
  ? await filterDesktop(args)
26
31
  : await filterMobile(args);
27
- const isLocal = await prepareLocalTunnel(args.url);
32
+ // Get credentials from config
33
+ const authString = getBrowserStackAuth(config);
34
+ const [username, password] = authString.split(":");
35
+ if (!username || !password) {
36
+ throw new Error("BrowserStack credentials are not set. Please configure them in the server settings.");
37
+ }
38
+ const isLocal = await prepareLocalTunnel(args.url, username, password);
28
39
  const url = args.platformType === PlatformType.DESKTOP
29
40
  ? buildDesktopUrl(args, entry, isLocal)
30
41
  : buildMobileUrl(args, entry, isLocal);
31
- openBrowser(url);
42
+ if (!envConfig.REMOTE_MCP) {
43
+ openBrowser(url);
44
+ }
32
45
  return entry.notes ? `${url}, ${entry.notes}` : url;
33
46
  }
34
47
  function buildDesktopUrl(args, e, isLocal) {
@@ -0,0 +1,33 @@
1
+ export interface DesktopSearchArgs {
2
+ platformType: "desktop";
3
+ url: string;
4
+ os: string;
5
+ osVersion: string;
6
+ browser: string;
7
+ browserVersion: string;
8
+ }
9
+ export interface DesktopEntry {
10
+ os: string;
11
+ os_version: string;
12
+ browser: string;
13
+ browser_version: string;
14
+ notes?: string;
15
+ }
16
+ export interface MobileSearchArgs {
17
+ platformType: "mobile";
18
+ url: string;
19
+ os: string;
20
+ osVersion: string;
21
+ device: string;
22
+ browser: string;
23
+ }
24
+ export interface MobileEntry {
25
+ os: string;
26
+ os_version: string;
27
+ display_name: string;
28
+ notes?: string;
29
+ }
30
+ export declare enum PlatformType {
31
+ DESKTOP = "desktop",
32
+ MOBILE = "mobile"
33
+ }
@@ -0,0 +1,3 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { BrowserStackConfig } from "../lib/types.js";
3
+ export default function addBrowserLiveTools(server: McpServer, config: BrowserStackConfig): Record<string, any>;
@@ -3,6 +3,7 @@ import logger from "../logger.js";
3
3
  import { startBrowserSession } from "./live-utils/start-session.js";
4
4
  import { PlatformType } from "./live-utils/types.js";
5
5
  import { trackMCP } from "../lib/instrumentation.js";
6
+ import globalConfig from "../config.js";
6
7
  // Define the schema shape
7
8
  const LiveArgsShape = {
8
9
  platformType: z
@@ -28,7 +29,7 @@ const LiveArgsSchema = z.object(LiveArgsShape);
28
29
  /**
29
30
  * Launches a desktop browser session
30
31
  */
31
- async function launchDesktopSession(args) {
32
+ async function launchDesktopSession(args, config) {
32
33
  if (!args.desiredBrowser)
33
34
  throw new Error("You must provide a desiredBrowser");
34
35
  if (!args.desiredBrowserVersion)
@@ -40,12 +41,12 @@ async function launchDesktopSession(args) {
40
41
  osVersion: args.desiredOSVersion,
41
42
  browser: args.desiredBrowser,
42
43
  browserVersion: args.desiredBrowserVersion,
43
- });
44
+ }, config);
44
45
  }
45
46
  /**
46
47
  * Launches a mobile browser session
47
48
  */
48
- async function launchMobileSession(args) {
49
+ async function launchMobileSession(args, config) {
49
50
  if (!args.desiredDevice)
50
51
  throw new Error("You must provide a desiredDevice");
51
52
  return startBrowserSession({
@@ -55,36 +56,46 @@ async function launchMobileSession(args) {
55
56
  os: args.desiredOS,
56
57
  osVersion: args.desiredOSVersion,
57
58
  device: args.desiredDevice,
58
- });
59
+ }, config);
59
60
  }
60
61
  /**
61
62
  * Handles the core logic for running a browser session
62
63
  */
63
- async function runBrowserSession(rawArgs) {
64
+ async function runBrowserSession(rawArgs, config) {
64
65
  // Validate and narrow
65
66
  const args = LiveArgsSchema.parse(rawArgs);
66
67
  // Branch desktop vs mobile and delegate
67
68
  const launchUrl = args.platformType === PlatformType.DESKTOP
68
- ? await launchDesktopSession(args)
69
- : await launchMobileSession(args);
70
- return {
71
- content: [
69
+ ? await launchDesktopSession(args, config)
70
+ : await launchMobileSession(args, config);
71
+ let response = [
72
+ {
73
+ type: "text",
74
+ text: `✅ Session started. If it didn't open automatically, visit:\n${launchUrl}`,
75
+ },
76
+ ];
77
+ if (globalConfig.REMOTE_MCP) {
78
+ response = [
72
79
  {
73
80
  type: "text",
74
- text: `✅ Session started. If it didn't open automatically, visit:\n${launchUrl}`,
81
+ text: `✅ To start the session. Click on ${launchUrl}`,
75
82
  },
76
- ],
83
+ ];
84
+ }
85
+ return {
86
+ content: response,
77
87
  };
78
88
  }
79
- export default function addBrowserLiveTools(server) {
80
- server.tool("runBrowserLiveSession", "Launch a BrowserStack Live session (desktop or mobile).", LiveArgsShape, async (args) => {
89
+ export default function addBrowserLiveTools(server, config) {
90
+ const tools = {};
91
+ tools.runBrowserLiveSession = server.tool("runBrowserLiveSession", "Launch a BrowserStack Live session (desktop or mobile).", LiveArgsShape, async (args) => {
81
92
  try {
82
- trackMCP("runBrowserLiveSession", server.server.getClientVersion());
83
- return await runBrowserSession(args);
93
+ trackMCP("runBrowserLiveSession", server.server.getClientVersion(), undefined, config);
94
+ return await runBrowserSession(args, config);
84
95
  }
85
96
  catch (error) {
86
97
  logger.error("Live session failed: %s", error);
87
- trackMCP("runBrowserLiveSession", server.server.getClientVersion(), error);
98
+ trackMCP("runBrowserLiveSession", server.server.getClientVersion(), error, config);
88
99
  return {
89
100
  content: [
90
101
  {
@@ -97,4 +108,5 @@ export default function addBrowserLiveTools(server) {
97
108
  };
98
109
  }
99
110
  });
111
+ return tools;
100
112
  }
@@ -0,0 +1,5 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
3
+ import { BrowserStackConfig } from "../lib/types.js";
4
+ export declare function getFailuresInLastRun(buildName: string, projectName: string, config: BrowserStackConfig): Promise<CallToolResult>;
5
+ export default function addObservabilityTools(server: McpServer, config: BrowserStackConfig): void;
@@ -2,19 +2,22 @@ import { z } from "zod";
2
2
  import { getLatestO11YBuildInfo } from "../lib/api.js";
3
3
  import { trackMCP } from "../lib/instrumentation.js";
4
4
  import logger from "../logger.js";
5
- export async function getFailuresInLastRun(buildName, projectName) {
6
- const buildsData = await getLatestO11YBuildInfo(buildName, projectName);
7
- const observabilityUrl = buildsData.observability_url;
5
+ export async function getFailuresInLastRun(buildName, projectName, config) {
6
+ const buildsData = await getLatestO11YBuildInfo(buildName, projectName, config);
7
+ if (!buildsData.data) {
8
+ throw new Error("No observability URL found in build data, this is likely because the build is not yet available on BrowserStack Observability.");
9
+ }
10
+ const observabilityUrl = buildsData.data.observability_url;
8
11
  if (!observabilityUrl) {
9
12
  throw new Error("No observability URL found in build data, this is likely because the build is not yet available on BrowserStack Observability.");
10
13
  }
11
14
  let overview = "No overview available";
12
- if (buildsData.unique_errors?.overview?.insight) {
13
- overview = buildsData.unique_errors.overview.insight;
15
+ if (buildsData.data.unique_errors?.overview?.insight) {
16
+ overview = buildsData.data.unique_errors.overview.insight;
14
17
  }
15
18
  let details = "No error details available";
16
- if (buildsData.unique_errors?.top_unique_errors?.length > 0) {
17
- details = buildsData.unique_errors.top_unique_errors
19
+ if (buildsData.data.unique_errors?.top_unique_errors?.length > 0) {
20
+ details = buildsData.data.unique_errors.top_unique_errors
18
21
  .map((error) => error.error)
19
22
  .filter(Boolean)
20
23
  .join("\n");
@@ -28,7 +31,7 @@ export async function getFailuresInLastRun(buildName, projectName) {
28
31
  ],
29
32
  };
30
33
  }
31
- export default function addObservabilityTools(server) {
34
+ export default function addObservabilityTools(server, config) {
32
35
  server.tool("getFailuresInLastRun", "Use this tool to debug failures in the last run of the test suite on BrowserStack. Use only when browserstack.yml file is present in the project root.", {
33
36
  buildName: z
34
37
  .string()
@@ -38,12 +41,12 @@ export default function addObservabilityTools(server) {
38
41
  .describe("Name of the project to get failures for. This is the 'projectName' key in the browserstack.yml file. If not sure, ask the user for the project name."),
39
42
  }, async (args) => {
40
43
  try {
41
- trackMCP("getFailuresInLastRun", server.server.getClientVersion());
42
- return await getFailuresInLastRun(args.buildName, args.projectName);
44
+ trackMCP("getFailuresInLastRun", server.server.getClientVersion(), undefined, config);
45
+ return await getFailuresInLastRun(args.buildName, args.projectName, config);
43
46
  }
44
47
  catch (error) {
45
48
  logger.error("Failed to get failures in the last run: %s", error);
46
- trackMCP("getFailuresInLastRun", server.server.getClientVersion(), error);
49
+ trackMCP("getFailuresInLastRun", server.server.getClientVersion(), error, config);
47
50
  return {
48
51
  content: [
49
52
  {
@@ -0,0 +1,3 @@
1
+ import { SDKSupportedLanguage } from "./types.js";
2
+ export declare function getSDKPrefixCommand(language: SDKSupportedLanguage, framework: string, username: string, accessKey: string): string;
3
+ export declare function getJavaFrameworkForMaven(framework: string): string;
@@ -4,6 +4,7 @@ const JAVA_FRAMEWORK_MAP = {
4
4
  junit5: "junit5",
5
5
  junit4: "junit4",
6
6
  cucumber: "cucumber-testng",
7
+ serenity: "serenity",
7
8
  };
8
9
  // Common Gradle setup instructions (platform-independent)
9
10
  const GRADLE_SETUP_INSTRUCTIONS = `
@@ -17,7 +18,7 @@ const GRADLE_SETUP_INSTRUCTIONS = `
17
18
  3. Add javaagent to gradle tasks:
18
19
  jvmArgs "-javaagent:\${browserstackSDKArtifact.file}"
19
20
  `;
20
- export function getSDKPrefixCommand(language, framework) {
21
+ export function getSDKPrefixCommand(language, framework, username, accessKey) {
21
22
  switch (language) {
22
23
  case "nodejs":
23
24
  return `---STEP---
@@ -28,7 +29,7 @@ npm i -D browserstack-node-sdk@latest
28
29
  ---STEP---
29
30
  Run the following command to setup browserstack sdk:
30
31
  \`\`\`bash
31
- npx setup --username ${process.env.BROWSERSTACK_USERNAME} --key ${process.env.BROWSERSTACK_ACCESS_KEY}
32
+ npx setup --username ${username} --key ${accessKey}
32
33
  \`\`\`
33
34
  ---STEP---
34
35
  Edit the browserstack.yml file that was created in the project root to add your desired platforms and browsers.`;
@@ -40,8 +41,8 @@ Edit the browserstack.yml file that was created in the project root to add your
40
41
  : `mvn archetype:generate -B -DarchetypeGroupId=com.browserstack \\
41
42
  -DarchetypeArtifactId=browserstack-sdk-archetype-integrate -DarchetypeVersion=1.0 \\
42
43
  -DgroupId=com.browserstack -DartifactId=browserstack-sdk-archetype-integrate -Dversion=1.0 \\
43
- -DBROWSERSTACK_USERNAME="${process.env.BROWSERSTACK_USERNAME}" \\
44
- -DBROWSERSTACK_ACCESS_KEY="${process.env.BROWSERSTACK_ACCESS_KEY}" \\
44
+ -DBROWSERSTACK_USERNAME="${username}" \\
45
+ -DBROWSERSTACK_ACCESS_KEY="${accessKey}" \\
45
46
  -DBROWSERSTACK_FRAMEWORK="${mavenFramework}"`;
46
47
  const platformLabel = isWindows ? "Windows" : "macOS/Linux";
47
48
  return `---STEP---
@@ -0,0 +1,2 @@
1
+ import { ConfigMapping } from "./types.js";
2
+ export declare const SUPPORTED_CONFIGURATIONS: ConfigMapping;