@browserstack/mcp-server 1.1.9 → 1.2.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 (123) hide show
  1. package/dist/config.d.ts +13 -0
  2. package/dist/config.js +4 -6
  3. package/dist/index.d.ts +4 -0
  4. package/dist/index.js +20 -30
  5. package/dist/lib/api.d.ts +2 -0
  6. package/dist/lib/api.js +10 -5
  7. package/dist/lib/apiClient.d.ts +31 -0
  8. package/dist/lib/apiClient.js +108 -0
  9. package/dist/lib/constants.d.ts +17 -0
  10. package/dist/lib/device-cache.d.ts +9 -0
  11. package/dist/lib/device-cache.js +3 -3
  12. package/dist/lib/error.d.ts +7 -0
  13. package/dist/lib/fuzzy.d.ts +1 -0
  14. package/dist/lib/get-auth.d.ts +2 -0
  15. package/dist/lib/get-auth.js +8 -0
  16. package/dist/lib/inmemory-store.d.ts +1 -0
  17. package/dist/lib/instrumentation.d.ts +4 -0
  18. package/dist/lib/instrumentation.js +8 -7
  19. package/dist/lib/local.d.ts +3 -0
  20. package/dist/lib/local.js +3 -3
  21. package/dist/lib/types.d.ts +4 -0
  22. package/dist/lib/types.js +1 -0
  23. package/dist/lib/utils.d.ts +4 -0
  24. package/dist/lib/version-resolver.d.ts +6 -0
  25. package/dist/logger.d.ts +3 -0
  26. package/dist/logger.js +20 -4
  27. package/dist/oninitialized.d.ts +2 -0
  28. package/dist/oninitialized.js +2 -7
  29. package/dist/server-factory.d.ts +3 -0
  30. package/dist/server-factory.js +37 -0
  31. package/dist/tools/accessibility.d.ts +3 -0
  32. package/dist/tools/accessibility.js +17 -10
  33. package/dist/tools/accessiblity-utils/accessibility-rag.d.ts +12 -0
  34. package/dist/tools/accessiblity-utils/accessibility-rag.js +22 -12
  35. package/dist/tools/accessiblity-utils/report-fetcher.d.ts +8 -0
  36. package/dist/tools/accessiblity-utils/report-fetcher.js +26 -16
  37. package/dist/tools/accessiblity-utils/report-parser.d.ts +23 -0
  38. package/dist/tools/accessiblity-utils/report-parser.js +3 -3
  39. package/dist/tools/accessiblity-utils/scanner.d.ts +25 -0
  40. package/dist/tools/accessiblity-utils/scanner.js +43 -24
  41. package/dist/tools/appautomate-utils/appautomate.d.ts +42 -0
  42. package/dist/tools/appautomate-utils/appautomate.js +63 -38
  43. package/dist/tools/appautomate-utils/types.d.ts +5 -0
  44. package/dist/tools/appautomate.d.ts +3 -0
  45. package/dist/tools/appautomate.js +20 -19
  46. package/dist/tools/applive-utils/device-search.d.ts +6 -0
  47. package/dist/tools/applive-utils/start-session.d.ts +15 -0
  48. package/dist/tools/applive-utils/start-session.js +11 -4
  49. package/dist/tools/applive-utils/types.d.ts +7 -0
  50. package/dist/tools/applive-utils/upload-app.d.ts +5 -0
  51. package/dist/tools/applive-utils/upload-app.js +8 -12
  52. package/dist/tools/applive-utils/version-utils.d.ts +4 -0
  53. package/dist/tools/applive.d.ts +13 -0
  54. package/dist/tools/applive.js +6 -6
  55. package/dist/tools/automate-utils/fetch-screenshots.d.ts +6 -0
  56. package/dist/tools/automate-utils/fetch-screenshots.js +16 -12
  57. package/dist/tools/automate.d.ts +9 -0
  58. package/dist/tools/automate.js +6 -6
  59. package/dist/tools/bstack-sdk.d.ts +17 -0
  60. package/dist/tools/bstack-sdk.js +12 -7
  61. package/dist/tools/failurelogs-utils/app-automate.d.ts +7 -0
  62. package/dist/tools/failurelogs-utils/app-automate.js +29 -11
  63. package/dist/tools/failurelogs-utils/automate.d.ts +6 -0
  64. package/dist/tools/failurelogs-utils/automate.js +27 -12
  65. package/dist/tools/failurelogs-utils/utils.d.ts +30 -0
  66. package/dist/tools/getFailureLogs.d.ts +14 -0
  67. package/dist/tools/getFailureLogs.js +11 -11
  68. package/dist/tools/live-utils/desktop-filter.d.ts +2 -0
  69. package/dist/tools/live-utils/mobile-filter.d.ts +2 -0
  70. package/dist/tools/live-utils/start-session.d.ts +6 -0
  71. package/dist/tools/live-utils/start-session.js +18 -5
  72. package/dist/tools/live-utils/types.d.ts +33 -0
  73. package/dist/tools/live.d.ts +3 -0
  74. package/dist/tools/live.js +11 -11
  75. package/dist/tools/observability.d.ts +5 -0
  76. package/dist/tools/observability.js +14 -11
  77. package/dist/tools/sdk-utils/commands.d.ts +3 -0
  78. package/dist/tools/sdk-utils/commands.js +5 -4
  79. package/dist/tools/sdk-utils/constants.d.ts +2 -0
  80. package/dist/tools/sdk-utils/constants.js +82 -21
  81. package/dist/tools/sdk-utils/instructions.d.ts +6 -0
  82. package/dist/tools/sdk-utils/instructions.js +8 -6
  83. package/dist/tools/sdk-utils/percy/constants.d.ts +3 -0
  84. package/dist/tools/sdk-utils/percy/constants.js +1 -0
  85. package/dist/tools/sdk-utils/percy/instructions.d.ts +10 -0
  86. package/dist/tools/sdk-utils/percy/types.d.ts +5 -0
  87. package/dist/tools/sdk-utils/types.d.ts +39 -0
  88. package/dist/tools/sdk-utils/types.js +1 -0
  89. package/dist/tools/selfheal-utils/selfheal.d.ts +11 -0
  90. package/dist/tools/selfheal-utils/selfheal.js +10 -6
  91. package/dist/tools/selfheal.d.ts +7 -0
  92. package/dist/tools/selfheal.js +6 -6
  93. package/dist/tools/testmanagement-utils/TCG-utils/api.d.ts +34 -0
  94. package/dist/tools/testmanagement-utils/TCG-utils/api.js +57 -44
  95. package/dist/tools/testmanagement-utils/TCG-utils/config.d.ts +5 -0
  96. package/dist/tools/testmanagement-utils/TCG-utils/helpers.d.ts +13 -0
  97. package/dist/tools/testmanagement-utils/TCG-utils/helpers.js +2 -1
  98. package/dist/tools/testmanagement-utils/TCG-utils/types.d.ts +26 -0
  99. package/dist/tools/testmanagement-utils/add-test-result.d.ts +42 -0
  100. package/dist/tools/testmanagement-utils/add-test-result.js +23 -10
  101. package/dist/tools/testmanagement-utils/create-lca-steps.d.ts +100 -0
  102. package/dist/tools/testmanagement-utils/create-lca-steps.js +64 -55
  103. package/dist/tools/testmanagement-utils/create-project-folder.d.ts +31 -0
  104. package/dist/tools/testmanagement-utils/create-project-folder.js +31 -21
  105. package/dist/tools/testmanagement-utils/create-testcase.d.ts +122 -0
  106. package/dist/tools/testmanagement-utils/create-testcase.js +13 -10
  107. package/dist/tools/testmanagement-utils/create-testrun.d.ts +82 -0
  108. package/dist/tools/testmanagement-utils/create-testrun.js +11 -8
  109. package/dist/tools/testmanagement-utils/list-testcases.d.ts +30 -0
  110. package/dist/tools/testmanagement-utils/list-testcases.js +9 -7
  111. package/dist/tools/testmanagement-utils/list-testruns.d.ts +22 -0
  112. package/dist/tools/testmanagement-utils/list-testruns.js +9 -7
  113. package/dist/tools/testmanagement-utils/poll-lca-status.d.ts +11 -0
  114. package/dist/tools/testmanagement-utils/poll-lca-status.js +12 -8
  115. package/dist/tools/testmanagement-utils/testcase-from-file.d.ts +4 -0
  116. package/dist/tools/testmanagement-utils/testcase-from-file.js +6 -6
  117. package/dist/tools/testmanagement-utils/update-testrun.d.ts +40 -0
  118. package/dist/tools/testmanagement-utils/update-testrun.js +11 -7
  119. package/dist/tools/testmanagement-utils/upload-file.d.ts +20 -0
  120. package/dist/tools/testmanagement-utils/upload-file.js +8 -6
  121. package/dist/tools/testmanagement.d.ts +60 -0
  122. package/dist/tools/testmanagement.js +51 -53
  123. package/package.json +1 -1
@@ -1,15 +1,18 @@
1
1
  import logger from "../../logger.js";
2
- import childProcess from "child_process";
3
2
  import { getDevicesAndBrowsers, BrowserStackProducts, } from "../../lib/device-cache.js";
4
3
  import { sanitizeUrlParam } from "../../lib/utils.js";
5
4
  import { uploadApp } from "./upload-app.js";
5
+ import { getBrowserStackAuth } from "../../lib/get-auth.js";
6
6
  import { findDeviceByName } from "./device-search.js";
7
7
  import { pickVersion } from "./version-utils.js";
8
+ import childProcess from "child_process";
9
+ import envConfig from "../../config.js";
8
10
  /**
9
11
  * Start an App Live session: filter, select, upload, and open.
10
12
  */
11
- export async function startSession(args) {
13
+ export async function startSession(args, options) {
12
14
  const { appPath, desiredPlatform, desiredPhone, desiredPlatformVersion } = args;
15
+ const { config } = options;
13
16
  // 1) Fetch devices for APP_LIVE
14
17
  const data = await getDevicesAndBrowsers(BrowserStackProducts.APP_LIVE);
15
18
  const all = data.mobile.flatMap((grp) => grp.devices.map((dev) => ({ ...dev, os: grp.os })));
@@ -36,7 +39,9 @@ export async function startSession(args) {
36
39
  note = `\n Note: The requested version "${desiredPlatformVersion}" is not available. Using "${version}" instead.`;
37
40
  }
38
41
  // 6) Upload app
39
- const { app_url } = await uploadApp(appPath);
42
+ const authString = getBrowserStackAuth(config);
43
+ const [username, password] = authString.split(":");
44
+ const { app_url } = await uploadApp(appPath, username, password);
40
45
  logger.info(`App uploaded: ${app_url}`);
41
46
  if (!app_url) {
42
47
  throw new Error("Failed to upload app. Please try again.");
@@ -52,7 +57,9 @@ export async function startSession(args) {
52
57
  start: "true",
53
58
  });
54
59
  const launchUrl = `https://app-live.browserstack.com/dashboard#${params.toString()}&device=${deviceParam}`;
55
- openBrowser(launchUrl);
60
+ if (!envConfig.REMOTE_MCP) {
61
+ openBrowser(launchUrl);
62
+ }
56
63
  return launchUrl + note;
57
64
  }
58
65
  /**
@@ -0,0 +1,7 @@
1
+ export interface DeviceEntry {
2
+ display_name: string;
3
+ device: string;
4
+ os: string;
5
+ os_version: string;
6
+ real_mobile: boolean;
7
+ }
@@ -0,0 +1,5 @@
1
+ interface UploadResponse {
2
+ app_url: string;
3
+ }
4
+ export declare function uploadApp(filePath: string, username: string, password: string): Promise<UploadResponse>;
5
+ export {};
@@ -1,29 +1,25 @@
1
- import axios, { AxiosError } from "axios";
1
+ import { apiClient } from "../../lib/apiClient.js";
2
2
  import FormData from "form-data";
3
3
  import fs from "fs";
4
- import config from "../../config.js";
5
- export async function uploadApp(filePath) {
4
+ export async function uploadApp(filePath, username, password) {
6
5
  if (!fs.existsSync(filePath)) {
7
6
  throw new Error(`File not found at path: ${filePath}`);
8
7
  }
9
8
  const formData = new FormData();
10
9
  formData.append("file", fs.createReadStream(filePath));
11
10
  try {
12
- const response = await axios.post("https://api-cloud.browserstack.com/app-live/upload", formData, {
11
+ const response = await apiClient.post({
12
+ url: "https://api-cloud.browserstack.com/app-live/upload",
13
13
  headers: {
14
14
  ...formData.getHeaders(),
15
+ Authorization: "Basic " + Buffer.from(`${username}:${password}`).toString("base64"),
15
16
  },
16
- auth: {
17
- username: config.browserstackUsername,
18
- password: config.browserstackAccessKey,
19
- },
17
+ body: formData,
20
18
  });
21
19
  return response.data;
22
20
  }
23
21
  catch (error) {
24
- if (error instanceof AxiosError) {
25
- throw new Error(`Failed to upload app: ${error.response?.data?.message || error.message}`);
26
- }
27
- throw error;
22
+ const msg = error?.response?.data?.message || error?.message || String(error);
23
+ throw new Error(`Failed to upload app: ${msg}`);
28
24
  }
29
25
  }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Resolve desired version against available list
3
+ */
4
+ export declare function pickVersion(available: string[], requested: string): string;
@@ -0,0 +1,13 @@
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
+ /**
5
+ * Launches an App Live Session on BrowserStack.
6
+ */
7
+ export declare function startAppLiveSession(args: {
8
+ desiredPlatform: string;
9
+ desiredPlatformVersion: string;
10
+ appPath: string;
11
+ desiredPhone: string;
12
+ }, config: BrowserStackConfig): Promise<CallToolResult>;
13
+ export default function addAppLiveTools(server: McpServer, config: BrowserStackConfig): void;
@@ -6,7 +6,7 @@ import { trackMCP } from "../lib/instrumentation.js";
6
6
  /**
7
7
  * Launches an App Live Session on BrowserStack.
8
8
  */
9
- export async function startAppLiveSession(args) {
9
+ export async function startAppLiveSession(args, config) {
10
10
  if (!args.desiredPlatform) {
11
11
  throw new Error("You must provide a desiredPlatform.");
12
12
  }
@@ -38,7 +38,7 @@ export async function startAppLiveSession(args) {
38
38
  desiredPlatform: args.desiredPlatform,
39
39
  desiredPhone: args.desiredPhone,
40
40
  desiredPlatformVersion: args.desiredPlatformVersion,
41
- });
41
+ }, { config });
42
42
  return {
43
43
  content: [
44
44
  {
@@ -48,7 +48,7 @@ export async function startAppLiveSession(args) {
48
48
  ],
49
49
  };
50
50
  }
51
- export default function addAppLiveTools(server) {
51
+ export default function addAppLiveTools(server, config) {
52
52
  server.tool("runAppLiveSession", "Use this tool when user wants to manually check their app on a particular mobile device using BrowserStack's cloud infrastructure. Can be used to debug crashes, slow performance, etc.", {
53
53
  desiredPhone: z
54
54
  .string()
@@ -64,12 +64,12 @@ export default function addAppLiveTools(server) {
64
64
  .describe("The path to the .ipa or .apk file to install on the device. Always ask the user for the app path, do not assume it."),
65
65
  }, async (args) => {
66
66
  try {
67
- trackMCP("runAppLiveSession", server.server.getClientVersion());
68
- return await startAppLiveSession(args);
67
+ trackMCP("runAppLiveSession", server.server.getClientVersion(), undefined, config);
68
+ return await startAppLiveSession(args, config);
69
69
  }
70
70
  catch (error) {
71
71
  logger.error("App live session failed: %s", error);
72
- trackMCP("runAppLiveSession", server.server.getClientVersion(), error);
72
+ trackMCP("runAppLiveSession", server.server.getClientVersion(), error, config);
73
73
  return {
74
74
  content: [
75
75
  {
@@ -0,0 +1,6 @@
1
+ import { SessionType } from "../../lib/constants.js";
2
+ import { BrowserStackConfig } from "../../lib/types.js";
3
+ export declare function fetchAutomationScreenshots(sessionId: string, sessionType: SessionType | undefined, config: BrowserStackConfig): Promise<{
4
+ url: string;
5
+ base64: string;
6
+ }[]>;
@@ -1,20 +1,24 @@
1
- import config from "../../config.js";
2
1
  import { assertOkResponse, maybeCompressBase64 } from "../../lib/utils.js";
3
2
  import { SessionType } from "../../lib/constants.js";
4
- //Extracts screenshot URLs from BrowserStack session logs
5
- async function extractScreenshotUrls(sessionId, sessionType) {
6
- const credentials = `${config.browserstackUsername}:${config.browserstackAccessKey}`;
7
- const auth = Buffer.from(credentials).toString("base64");
3
+ import { getBrowserStackAuth } from "../../lib/get-auth.js";
4
+ import { apiClient } from "../../lib/apiClient.js";
5
+ async function extractScreenshotUrls(sessionId, sessionType, config) {
6
+ const authString = getBrowserStackAuth(config);
7
+ const auth = Buffer.from(authString).toString("base64");
8
8
  const baseUrl = `https://api.browserstack.com/${sessionType === SessionType.Automate ? "automate" : "app-automate"}`;
9
9
  const url = `${baseUrl}/sessions/${sessionId}/logs`;
10
- const response = await fetch(url, {
10
+ const response = await apiClient.get({
11
+ url,
11
12
  headers: {
12
13
  "Content-Type": "application/json",
13
14
  Authorization: `Basic ${auth}`,
14
15
  },
16
+ raise_error: false,
15
17
  });
16
18
  await assertOkResponse(response, "Session");
17
- const text = await response.text();
19
+ const text = typeof response.data === "string"
20
+ ? response.data
21
+ : JSON.stringify(response.data);
18
22
  const urls = [];
19
23
  const SCREENSHOT_PATTERN = /REQUEST.*GET.*\/screenshot/;
20
24
  const RESPONSE_VALUE_PATTERN = /"value"\s*:\s*"([^"]+)"/;
@@ -35,9 +39,9 @@ async function extractScreenshotUrls(sessionId, sessionType) {
35
39
  //Converts screenshot URLs to base64 encoded images
36
40
  async function convertUrlsToBase64(urls) {
37
41
  const screenshots = await Promise.all(urls.map(async (url) => {
38
- const response = await fetch(url);
39
- const arrayBuffer = await response.arrayBuffer();
40
- const base64 = Buffer.from(arrayBuffer).toString("base64");
42
+ const response = await apiClient.get({ url });
43
+ // Axios returns response.data as a Buffer for binary data
44
+ const base64 = Buffer.from(response.data).toString("base64");
41
45
  // Compress the base64 image if needed
42
46
  const compressedBase64 = await maybeCompressBase64(base64);
43
47
  return {
@@ -48,8 +52,8 @@ async function convertUrlsToBase64(urls) {
48
52
  return screenshots;
49
53
  }
50
54
  //Fetches and converts screenshot URLs to base64 encoded images
51
- export async function fetchAutomationScreenshots(sessionId, sessionType = SessionType.Automate) {
52
- const urls = await extractScreenshotUrls(sessionId, sessionType);
55
+ export async function fetchAutomationScreenshots(sessionId, sessionType = SessionType.Automate, config) {
56
+ const urls = await extractScreenshotUrls(sessionId, sessionType, config);
53
57
  if (urls.length === 0) {
54
58
  return [];
55
59
  }
@@ -0,0 +1,9 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
3
+ import { SessionType } from "../lib/constants.js";
4
+ import { BrowserStackConfig } from "../lib/types.js";
5
+ export declare function fetchAutomationScreenshotsTool(args: {
6
+ sessionId: string;
7
+ sessionType: SessionType;
8
+ }, config: BrowserStackConfig): Promise<CallToolResult>;
9
+ export default function addAutomationTools(server: McpServer, config: BrowserStackConfig): void;
@@ -4,9 +4,9 @@ import { SessionType } from "../lib/constants.js";
4
4
  import { trackMCP } from "../lib/instrumentation.js";
5
5
  import logger from "../logger.js";
6
6
  // Tool function that fetches and processes screenshots from BrowserStack Automate session
7
- export async function fetchAutomationScreenshotsTool(args) {
7
+ export async function fetchAutomationScreenshotsTool(args, config) {
8
8
  try {
9
- const screenshots = await fetchAutomationScreenshots(args.sessionId, args.sessionType);
9
+ const screenshots = await fetchAutomationScreenshots(args.sessionId, args.sessionType, config);
10
10
  if (screenshots.length === 0) {
11
11
  return {
12
12
  content: [
@@ -41,7 +41,7 @@ export async function fetchAutomationScreenshotsTool(args) {
41
41
  }
42
42
  }
43
43
  //Registers the fetchAutomationScreenshots tool with the MCP server
44
- export default function addAutomationTools(server) {
44
+ export default function addAutomationTools(server, config) {
45
45
  server.tool("fetchAutomationScreenshots", "Fetch and process screenshots from a BrowserStack Automate session", {
46
46
  sessionId: z
47
47
  .string()
@@ -51,11 +51,11 @@ export default function addAutomationTools(server) {
51
51
  .describe("Type of BrowserStack session"),
52
52
  }, async (args) => {
53
53
  try {
54
- trackMCP("fetchAutomationScreenshots", server.server.getClientVersion());
55
- return await fetchAutomationScreenshotsTool(args);
54
+ trackMCP("fetchAutomationScreenshots", server.server.getClientVersion(), undefined, config);
55
+ return await fetchAutomationScreenshotsTool(args, config);
56
56
  }
57
57
  catch (error) {
58
- trackMCP("fetchAutomationScreenshots", server.server.getClientVersion(), error);
58
+ trackMCP("fetchAutomationScreenshots", server.server.getClientVersion(), error, config);
59
59
  const errorMessage = error instanceof Error ? error.message : "Unknown error";
60
60
  return {
61
61
  content: [
@@ -0,0 +1,17 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
3
+ import { SDKSupportedBrowserAutomationFramework, SDKSupportedLanguage, SDKSupportedTestingFramework } from "./sdk-utils/types.js";
4
+ import { BrowserStackConfig } from "../lib/types.js";
5
+ /**
6
+ * BrowserStack SDK hooks into your test framework to seamlessly run tests on BrowserStack.
7
+ * This tool gives instructions to setup a browserstack.yml file in the project root and installs the necessary dependencies.
8
+ */
9
+ export declare function bootstrapProjectWithSDK({ detectedBrowserAutomationFramework, detectedTestingFramework, detectedLanguage, desiredPlatforms, enablePercy, config, }: {
10
+ detectedBrowserAutomationFramework: SDKSupportedBrowserAutomationFramework;
11
+ detectedTestingFramework: SDKSupportedTestingFramework;
12
+ detectedLanguage: SDKSupportedLanguage;
13
+ desiredPlatforms: string[];
14
+ enablePercy: boolean;
15
+ config: BrowserStackConfig;
16
+ }): Promise<CallToolResult>;
17
+ export default function addSDKTools(server: McpServer, config: BrowserStackConfig): void;
@@ -4,15 +4,19 @@ import { getSDKPrefixCommand } from "./sdk-utils/commands.js";
4
4
  import { SDKSupportedLanguageEnum, SDKSupportedBrowserAutomationFrameworkEnum, SDKSupportedTestingFrameworkEnum, } from "./sdk-utils/types.js";
5
5
  import { generateBrowserStackYMLInstructions, getInstructionsForProjectConfiguration, formatInstructionsWithNumbers, } from "./sdk-utils/instructions.js";
6
6
  import { formatPercyInstructions, getPercyInstructions, } from "./sdk-utils/percy/instructions.js";
7
+ import { getBrowserStackAuth } from "../lib/get-auth.js";
7
8
  /**
8
9
  * BrowserStack SDK hooks into your test framework to seamlessly run tests on BrowserStack.
9
10
  * This tool gives instructions to setup a browserstack.yml file in the project root and installs the necessary dependencies.
10
11
  */
11
- export async function bootstrapProjectWithSDK({ detectedBrowserAutomationFramework, detectedTestingFramework, detectedLanguage, desiredPlatforms, enablePercy, }) {
12
+ export async function bootstrapProjectWithSDK({ detectedBrowserAutomationFramework, detectedTestingFramework, detectedLanguage, desiredPlatforms, enablePercy, config, }) {
13
+ // Get credentials from config
14
+ const authString = getBrowserStackAuth(config);
15
+ const [username, accessKey] = authString.split(":");
12
16
  // Handle frameworks with unique setup instructions that don't use browserstack.yml
13
17
  if (detectedBrowserAutomationFramework === "cypress" ||
14
18
  detectedTestingFramework === "webdriverio") {
15
- let combinedInstructions = getInstructionsForProjectConfiguration(detectedBrowserAutomationFramework, detectedTestingFramework, detectedLanguage);
19
+ let combinedInstructions = getInstructionsForProjectConfiguration(detectedBrowserAutomationFramework, detectedTestingFramework, detectedLanguage, username, accessKey);
16
20
  if (enablePercy) {
17
21
  const percyInstructions = getPercyInstructions(detectedLanguage, detectedBrowserAutomationFramework, detectedTestingFramework);
18
22
  if (percyInstructions) {
@@ -27,9 +31,9 @@ export async function bootstrapProjectWithSDK({ detectedBrowserAutomationFramewo
27
31
  return formatFinalInstructions(combinedInstructions);
28
32
  }
29
33
  // Handle default flow using browserstack.yml
30
- const sdkSetupCommand = getSDKPrefixCommand(detectedLanguage, detectedTestingFramework);
34
+ const sdkSetupCommand = getSDKPrefixCommand(detectedLanguage, detectedTestingFramework, username, accessKey);
31
35
  const ymlInstructions = generateBrowserStackYMLInstructions(desiredPlatforms, enablePercy);
32
- const instructionsForProjectConfiguration = getInstructionsForProjectConfiguration(detectedBrowserAutomationFramework, detectedTestingFramework, detectedLanguage);
36
+ const instructionsForProjectConfiguration = getInstructionsForProjectConfiguration(detectedBrowserAutomationFramework, detectedTestingFramework, detectedLanguage, username, accessKey);
33
37
  let combinedInstructions = "";
34
38
  // Step 1: Add SDK setup command
35
39
  if (sdkSetupCommand) {
@@ -74,7 +78,7 @@ function formatFinalInstructions(combinedInstructions) {
74
78
  ],
75
79
  };
76
80
  }
77
- export default function addSDKTools(server) {
81
+ export default function addSDKTools(server, config) {
78
82
  server.tool("runTestsOnBrowserStack", "Use this tool to get instructions for running tests on BrowserStack and BrowserStack Percy. It sets up the BrowserStack SDK and runs your test cases on BrowserStack.", {
79
83
  detectedBrowserAutomationFramework: z
80
84
  .nativeEnum(SDKSupportedBrowserAutomationFrameworkEnum)
@@ -95,17 +99,18 @@ export default function addSDKTools(server) {
95
99
  .describe("Set to true if the user wants to enable Percy for visual testing. Defaults to false."),
96
100
  }, async (args) => {
97
101
  try {
98
- trackMCP("runTestsOnBrowserStack", server.server.getClientVersion());
102
+ trackMCP("runTestsOnBrowserStack", server.server.getClientVersion(), undefined, config);
99
103
  return await bootstrapProjectWithSDK({
100
104
  detectedBrowserAutomationFramework: args.detectedBrowserAutomationFramework,
101
105
  detectedTestingFramework: args.detectedTestingFramework,
102
106
  detectedLanguage: args.detectedLanguage,
103
107
  desiredPlatforms: args.desiredPlatforms,
104
108
  enablePercy: args.enablePercy,
109
+ config,
105
110
  });
106
111
  }
107
112
  catch (error) {
108
- trackMCP("runTestsOnBrowserStack", server.server.getClientVersion(), error);
113
+ trackMCP("runTestsOnBrowserStack", server.server.getClientVersion(), error, config);
109
114
  return {
110
115
  content: [
111
116
  {
@@ -0,0 +1,7 @@
1
+ import { BrowserStackConfig } from "../../lib/types.js";
2
+ export declare function retrieveDeviceLogs(sessionId: string, buildId: string, config: BrowserStackConfig): Promise<string>;
3
+ export declare function retrieveAppiumLogs(sessionId: string, buildId: string, config: BrowserStackConfig): Promise<string>;
4
+ export declare function retrieveCrashLogs(sessionId: string, buildId: string, config: BrowserStackConfig): Promise<string>;
5
+ export declare function filterDeviceFailures(logText: string): string[];
6
+ export declare function filterAppiumFailures(logText: string): string[];
7
+ export declare function filterCrashFailures(logText: string): string[];
@@ -1,55 +1,73 @@
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
  // DEVICE LOGS
5
- export async function retrieveDeviceLogs(sessionId, buildId) {
5
+ export async function retrieveDeviceLogs(sessionId, buildId, config) {
6
6
  const url = `https://api.browserstack.com/app-automate/builds/${buildId}/sessions/${sessionId}/deviceLogs`;
7
- const response = await fetch(url, {
7
+ const authString = getBrowserStackAuth(config);
8
+ const auth = Buffer.from(authString).toString("base64");
9
+ const response = await apiClient.get({
10
+ url,
8
11
  headers: {
9
12
  "Content-Type": "application/json",
10
13
  Authorization: `Basic ${auth}`,
11
14
  },
15
+ raise_error: false,
12
16
  });
13
17
  const validationError = validateLogResponse(response, "device logs");
14
18
  if (validationError)
15
19
  return validationError.message;
16
- const logText = await response.text();
20
+ const logText = typeof response.data === "string"
21
+ ? response.data
22
+ : JSON.stringify(response.data);
17
23
  const logs = filterDeviceFailures(logText);
18
24
  return logs.length > 0
19
25
  ? `Device Failures (${logs.length} found):\n${JSON.stringify(logs, null, 2)}`
20
26
  : "No device failures found";
21
27
  }
22
28
  // APPIUM LOGS
23
- export async function retrieveAppiumLogs(sessionId, buildId) {
29
+ export async function retrieveAppiumLogs(sessionId, buildId, config) {
24
30
  const url = `https://api.browserstack.com/app-automate/builds/${buildId}/sessions/${sessionId}/appiumlogs`;
25
- const response = await fetch(url, {
31
+ const authString = getBrowserStackAuth(config);
32
+ const auth = Buffer.from(authString).toString("base64");
33
+ const response = await apiClient.get({
34
+ url,
26
35
  headers: {
27
36
  "Content-Type": "application/json",
28
37
  Authorization: `Basic ${auth}`,
29
38
  },
39
+ raise_error: false,
30
40
  });
31
41
  const validationError = validateLogResponse(response, "Appium logs");
32
42
  if (validationError)
33
43
  return validationError.message;
34
- const logText = await response.text();
44
+ const logText = typeof response.data === "string"
45
+ ? response.data
46
+ : JSON.stringify(response.data);
35
47
  const logs = filterAppiumFailures(logText);
36
48
  return logs.length > 0
37
49
  ? `Appium Failures (${logs.length} found):\n${JSON.stringify(logs, null, 2)}`
38
50
  : "No Appium failures found";
39
51
  }
40
52
  // CRASH LOGS
41
- export async function retrieveCrashLogs(sessionId, buildId) {
53
+ export async function retrieveCrashLogs(sessionId, buildId, config) {
42
54
  const url = `https://api.browserstack.com/app-automate/builds/${buildId}/sessions/${sessionId}/crashlogs`;
43
- const response = await fetch(url, {
55
+ const authString = getBrowserStackAuth(config);
56
+ const auth = Buffer.from(authString).toString("base64");
57
+ const response = await apiClient.get({
58
+ url,
44
59
  headers: {
45
60
  "Content-Type": "application/json",
46
61
  Authorization: `Basic ${auth}`,
47
62
  },
63
+ raise_error: false,
48
64
  });
49
65
  const validationError = validateLogResponse(response, "crash logs");
50
66
  if (validationError)
51
67
  return validationError.message;
52
- const logText = await response.text();
68
+ const logText = typeof response.data === "string"
69
+ ? response.data
70
+ : JSON.stringify(response.data);
53
71
  const logs = filterCrashFailures(logText);
54
72
  return logs.length > 0
55
73
  ? `Crash Failures (${logs.length} found):\n${JSON.stringify(logs, null, 2)}`
@@ -0,0 +1,6 @@
1
+ import { BrowserStackConfig } from "../../lib/types.js";
2
+ export declare function retrieveNetworkFailures(sessionId: string, config: BrowserStackConfig): Promise<string>;
3
+ export declare function retrieveSessionFailures(sessionId: string, config: BrowserStackConfig): Promise<string>;
4
+ export declare function retrieveConsoleFailures(sessionId: string, config: BrowserStackConfig): Promise<string>;
5
+ export declare function filterSessionFailures(logText: string): string[];
6
+ export declare function filterConsoleFailures(logText: string): string[];
@@ -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): void;
14
+ export {};