@browserstack/mcp-server 1.1.8 → 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 (125) hide show
  1. package/README.md +71 -35
  2. package/dist/config.d.ts +13 -0
  3. package/dist/config.js +10 -6
  4. package/dist/index.d.ts +4 -0
  5. package/dist/index.js +20 -30
  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 +12 -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 +3 -0
  31. package/dist/server-factory.js +37 -0
  32. package/dist/tools/accessibility.d.ts +3 -0
  33. package/dist/tools/accessibility.js +17 -10
  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 +46 -24
  42. package/dist/tools/appautomate-utils/appautomate.d.ts +42 -0
  43. package/dist/tools/appautomate-utils/appautomate.js +95 -9
  44. package/dist/tools/appautomate-utils/types.d.ts +5 -0
  45. package/dist/tools/appautomate-utils/types.js +6 -0
  46. package/dist/tools/appautomate.d.ts +3 -0
  47. package/dist/tools/appautomate.js +109 -13
  48. package/dist/tools/applive-utils/device-search.d.ts +6 -0
  49. package/dist/tools/applive-utils/start-session.d.ts +15 -0
  50. package/dist/tools/applive-utils/start-session.js +11 -4
  51. package/dist/tools/applive-utils/types.d.ts +7 -0
  52. package/dist/tools/applive-utils/upload-app.d.ts +5 -0
  53. package/dist/tools/applive-utils/upload-app.js +8 -12
  54. package/dist/tools/applive-utils/version-utils.d.ts +4 -0
  55. package/dist/tools/applive.d.ts +13 -0
  56. package/dist/tools/applive.js +6 -6
  57. package/dist/tools/automate-utils/fetch-screenshots.d.ts +6 -0
  58. package/dist/tools/automate-utils/fetch-screenshots.js +16 -12
  59. package/dist/tools/automate.d.ts +9 -0
  60. package/dist/tools/automate.js +6 -6
  61. package/dist/tools/bstack-sdk.d.ts +17 -0
  62. package/dist/tools/bstack-sdk.js +47 -20
  63. package/dist/tools/failurelogs-utils/app-automate.d.ts +7 -0
  64. package/dist/tools/failurelogs-utils/app-automate.js +29 -11
  65. package/dist/tools/failurelogs-utils/automate.d.ts +6 -0
  66. package/dist/tools/failurelogs-utils/automate.js +27 -12
  67. package/dist/tools/failurelogs-utils/utils.d.ts +30 -0
  68. package/dist/tools/getFailureLogs.d.ts +14 -0
  69. package/dist/tools/getFailureLogs.js +11 -11
  70. package/dist/tools/live-utils/desktop-filter.d.ts +2 -0
  71. package/dist/tools/live-utils/mobile-filter.d.ts +2 -0
  72. package/dist/tools/live-utils/start-session.d.ts +6 -0
  73. package/dist/tools/live-utils/start-session.js +18 -5
  74. package/dist/tools/live-utils/types.d.ts +33 -0
  75. package/dist/tools/live.d.ts +3 -0
  76. package/dist/tools/live.js +11 -11
  77. package/dist/tools/observability.d.ts +5 -0
  78. package/dist/tools/observability.js +14 -11
  79. package/dist/tools/sdk-utils/commands.d.ts +3 -0
  80. package/dist/tools/sdk-utils/commands.js +20 -5
  81. package/dist/tools/sdk-utils/constants.d.ts +2 -0
  82. package/dist/tools/sdk-utils/constants.js +284 -160
  83. package/dist/tools/sdk-utils/instructions.d.ts +6 -0
  84. package/dist/tools/sdk-utils/instructions.js +28 -6
  85. package/dist/tools/sdk-utils/percy/constants.d.ts +3 -0
  86. package/dist/tools/sdk-utils/percy/constants.js +36 -25
  87. package/dist/tools/sdk-utils/percy/instructions.d.ts +10 -0
  88. package/dist/tools/sdk-utils/percy/types.d.ts +5 -0
  89. package/dist/tools/sdk-utils/types.d.ts +39 -0
  90. package/dist/tools/sdk-utils/types.js +3 -0
  91. package/dist/tools/selfheal-utils/selfheal.d.ts +11 -0
  92. package/dist/tools/selfheal-utils/selfheal.js +10 -6
  93. package/dist/tools/selfheal.d.ts +7 -0
  94. package/dist/tools/selfheal.js +6 -6
  95. package/dist/tools/testmanagement-utils/TCG-utils/api.d.ts +34 -0
  96. package/dist/tools/testmanagement-utils/TCG-utils/api.js +57 -44
  97. package/dist/tools/testmanagement-utils/TCG-utils/config.d.ts +5 -0
  98. package/dist/tools/testmanagement-utils/TCG-utils/helpers.d.ts +13 -0
  99. package/dist/tools/testmanagement-utils/TCG-utils/helpers.js +2 -1
  100. package/dist/tools/testmanagement-utils/TCG-utils/types.d.ts +26 -0
  101. package/dist/tools/testmanagement-utils/add-test-result.d.ts +42 -0
  102. package/dist/tools/testmanagement-utils/add-test-result.js +23 -10
  103. package/dist/tools/testmanagement-utils/create-lca-steps.d.ts +100 -0
  104. package/dist/tools/testmanagement-utils/create-lca-steps.js +64 -55
  105. package/dist/tools/testmanagement-utils/create-project-folder.d.ts +31 -0
  106. package/dist/tools/testmanagement-utils/create-project-folder.js +31 -21
  107. package/dist/tools/testmanagement-utils/create-testcase.d.ts +122 -0
  108. package/dist/tools/testmanagement-utils/create-testcase.js +13 -10
  109. package/dist/tools/testmanagement-utils/create-testrun.d.ts +82 -0
  110. package/dist/tools/testmanagement-utils/create-testrun.js +11 -8
  111. package/dist/tools/testmanagement-utils/list-testcases.d.ts +30 -0
  112. package/dist/tools/testmanagement-utils/list-testcases.js +9 -7
  113. package/dist/tools/testmanagement-utils/list-testruns.d.ts +22 -0
  114. package/dist/tools/testmanagement-utils/list-testruns.js +9 -7
  115. package/dist/tools/testmanagement-utils/poll-lca-status.d.ts +11 -0
  116. package/dist/tools/testmanagement-utils/poll-lca-status.js +12 -8
  117. package/dist/tools/testmanagement-utils/testcase-from-file.d.ts +4 -0
  118. package/dist/tools/testmanagement-utils/testcase-from-file.js +6 -6
  119. package/dist/tools/testmanagement-utils/update-testrun.d.ts +40 -0
  120. package/dist/tools/testmanagement-utils/update-testrun.js +11 -7
  121. package/dist/tools/testmanagement-utils/upload-file.d.ts +20 -0
  122. package/dist/tools/testmanagement-utils/upload-file.js +8 -6
  123. package/dist/tools/testmanagement.d.ts +60 -0
  124. package/dist/tools/testmanagement.js +51 -53
  125. package/package.json +1 -1
@@ -1,47 +1,73 @@
1
1
  import { z } from "zod";
2
- import { SDKSupportedLanguageEnum, SDKSupportedBrowserAutomationFrameworkEnum, SDKSupportedTestingFrameworkEnum, } from "./sdk-utils/types.js";
3
- import { generateBrowserStackYMLInstructions, getInstructionsForProjectConfiguration, } from "./sdk-utils/instructions.js";
4
2
  import { trackMCP } from "../lib/instrumentation.js";
5
- import { formatPercyInstructions, getPercyInstructions, } from "./sdk-utils/percy/instructions.js";
6
3
  import { getSDKPrefixCommand } from "./sdk-utils/commands.js";
4
+ import { SDKSupportedLanguageEnum, SDKSupportedBrowserAutomationFrameworkEnum, SDKSupportedTestingFrameworkEnum, } from "./sdk-utils/types.js";
5
+ import { generateBrowserStackYMLInstructions, getInstructionsForProjectConfiguration, formatInstructionsWithNumbers, } from "./sdk-utils/instructions.js";
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 instructions = 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) {
19
- instructions += formatPercyInstructions(percyInstructions);
23
+ combinedInstructions +=
24
+ "\n\n" + formatPercyInstructions(percyInstructions);
20
25
  }
21
26
  else {
22
27
  throw new Error(`Percy is currently not supported through MCP for ${detectedLanguage} with ${detectedTestingFramework}. If you want to run the test cases without Percy, disable Percy and run it again.`);
23
28
  }
24
29
  }
25
- return {
26
- content: [{ type: "text", text: instructions, isError: false }],
27
- };
30
+ // Apply consistent formatting for all configurations
31
+ return formatFinalInstructions(combinedInstructions);
28
32
  }
29
- let fullInstructions = "";
30
- // Add language-dependent prefix command
31
- fullInstructions += getSDKPrefixCommand(detectedLanguage, detectedTestingFramework);
33
+ // Handle default flow using browserstack.yml
34
+ const sdkSetupCommand = getSDKPrefixCommand(detectedLanguage, detectedTestingFramework, username, accessKey);
32
35
  const ymlInstructions = generateBrowserStackYMLInstructions(desiredPlatforms, enablePercy);
33
- fullInstructions += `${ymlInstructions}`;
34
- const instructionsForProjectConfiguration = getInstructionsForProjectConfiguration(detectedBrowserAutomationFramework, detectedTestingFramework, detectedLanguage);
36
+ const instructionsForProjectConfiguration = getInstructionsForProjectConfiguration(detectedBrowserAutomationFramework, detectedTestingFramework, detectedLanguage, username, accessKey);
37
+ let combinedInstructions = "";
38
+ // Step 1: Add SDK setup command
39
+ if (sdkSetupCommand) {
40
+ combinedInstructions += sdkSetupCommand;
41
+ }
42
+ // Step 2: Add browserstack.yml setup
43
+ if (ymlInstructions) {
44
+ combinedInstructions += "\n\n---STEP---\n" + ymlInstructions;
45
+ }
46
+ // Step 3: Add language/framework-specific setup
47
+ if (instructionsForProjectConfiguration) {
48
+ combinedInstructions += "\n\n" + instructionsForProjectConfiguration;
49
+ }
50
+ // Step 4: Add Percy setup if applicable
35
51
  if (enablePercy) {
36
52
  const percyInstructions = getPercyInstructions(detectedLanguage, detectedBrowserAutomationFramework, detectedTestingFramework);
37
53
  if (percyInstructions) {
38
- fullInstructions += formatPercyInstructions(percyInstructions);
54
+ combinedInstructions +=
55
+ "\n\n" + formatPercyInstructions(percyInstructions);
39
56
  }
40
57
  else {
41
58
  throw new Error(`Percy is currently not supported through MCP for ${detectedLanguage} with ${detectedTestingFramework}. If you want to run the test cases without Percy, disable Percy and run it again.`);
42
59
  }
43
60
  }
44
- fullInstructions += `\n\nAfter setting up the files above, follow these final steps:\n${instructionsForProjectConfiguration}`;
61
+ // Apply consistent formatting for all configurations
62
+ return formatFinalInstructions(combinedInstructions);
63
+ }
64
+ // Helper function to apply consistent formatting to all instruction types
65
+ function formatFinalInstructions(combinedInstructions) {
66
+ const fullInstructions = `⚠️ IMPORTANT: DO NOT SKIP ANY STEP
67
+ All the setup steps described in this file MUST be executed regardless of any existing configuration or setup.
68
+ This ensures proper BrowserStack SDK setup.
69
+
70
+ ${formatInstructionsWithNumbers(combinedInstructions)}`;
45
71
  return {
46
72
  content: [
47
73
  {
@@ -52,8 +78,8 @@ export async function bootstrapProjectWithSDK({ detectedBrowserAutomationFramewo
52
78
  ],
53
79
  };
54
80
  }
55
- export default function addSDKTools(server) {
56
- server.tool("runTestsOnBrowserStack", "Use this tool to get instructions for running tests on BrowserStack and browserstack percy", {
81
+ export default function addSDKTools(server, config) {
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.", {
57
83
  detectedBrowserAutomationFramework: z
58
84
  .nativeEnum(SDKSupportedBrowserAutomationFrameworkEnum)
59
85
  .describe("The automation framework configured in the project. Example: 'playwright', 'selenium'"),
@@ -73,17 +99,18 @@ export default function addSDKTools(server) {
73
99
  .describe("Set to true if the user wants to enable Percy for visual testing. Defaults to false."),
74
100
  }, async (args) => {
75
101
  try {
76
- trackMCP("runTestsOnBrowserStack", server.server.getClientVersion());
102
+ trackMCP("runTestsOnBrowserStack", server.server.getClientVersion(), undefined, config);
77
103
  return await bootstrapProjectWithSDK({
78
104
  detectedBrowserAutomationFramework: args.detectedBrowserAutomationFramework,
79
105
  detectedTestingFramework: args.detectedTestingFramework,
80
106
  detectedLanguage: args.detectedLanguage,
81
107
  desiredPlatforms: args.desiredPlatforms,
82
108
  enablePercy: args.enablePercy,
109
+ config,
83
110
  });
84
111
  }
85
112
  catch (error) {
86
- trackMCP("runTestsOnBrowserStack", server.server.getClientVersion(), error);
113
+ trackMCP("runTestsOnBrowserStack", server.server.getClientVersion(), error, config);
87
114
  return {
88
115
  content: [
89
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 {};
@@ -5,7 +5,7 @@ import { retrieveNetworkFailures, retrieveSessionFailures, retrieveConsoleFailur
5
5
  import { retrieveDeviceLogs, retrieveAppiumLogs, retrieveCrashLogs, } from "./failurelogs-utils/app-automate.js";
6
6
  import { AppAutomateLogType, AutomateLogType, SessionType, } from "../lib/constants.js";
7
7
  // Main log fetcher function
8
- export async function getFailureLogs(args) {
8
+ export async function getFailureLogs(args, config) {
9
9
  const results = [];
10
10
  const errors = [];
11
11
  let validLogTypes = [];
@@ -53,32 +53,32 @@ export async function getFailureLogs(args) {
53
53
  for (const logType of validLogTypes) {
54
54
  switch (logType) {
55
55
  case AutomateLogType.NetworkLogs: {
56
- response = await retrieveNetworkFailures(args.sessionId);
56
+ response = await retrieveNetworkFailures(args.sessionId, config);
57
57
  results.push({ type: "text", text: response });
58
58
  break;
59
59
  }
60
60
  case AutomateLogType.SessionLogs: {
61
- response = await retrieveSessionFailures(args.sessionId);
61
+ response = await retrieveSessionFailures(args.sessionId, config);
62
62
  results.push({ type: "text", text: response });
63
63
  break;
64
64
  }
65
65
  case AutomateLogType.ConsoleLogs: {
66
- response = await retrieveConsoleFailures(args.sessionId);
66
+ response = await retrieveConsoleFailures(args.sessionId, config);
67
67
  results.push({ type: "text", text: response });
68
68
  break;
69
69
  }
70
70
  case AppAutomateLogType.DeviceLogs: {
71
- response = await retrieveDeviceLogs(args.sessionId, args.buildId);
71
+ response = await retrieveDeviceLogs(args.sessionId, args.buildId, config);
72
72
  results.push({ type: "text", text: response });
73
73
  break;
74
74
  }
75
75
  case AppAutomateLogType.AppiumLogs: {
76
- response = await retrieveAppiumLogs(args.sessionId, args.buildId);
76
+ response = await retrieveAppiumLogs(args.sessionId, args.buildId, config);
77
77
  results.push({ type: "text", text: response });
78
78
  break;
79
79
  }
80
80
  case AppAutomateLogType.CrashLogs: {
81
- response = await retrieveCrashLogs(args.sessionId, args.buildId);
81
+ response = await retrieveCrashLogs(args.sessionId, args.buildId, config);
82
82
  results.push({ type: "text", text: response });
83
83
  break;
84
84
  }
@@ -98,7 +98,7 @@ export async function getFailureLogs(args) {
98
98
  return { content: results };
99
99
  }
100
100
  // Register tool with the MCP server
101
- export default function registerGetFailureLogs(server) {
101
+ export default function registerGetFailureLogs(server, config) {
102
102
  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])
@@ -122,12 +122,12 @@ 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
129
  const message = error instanceof Error ? error.message : String(error);
130
- trackMCP("getFailureLogs", server.server.getClientVersion(), error);
130
+ trackMCP("getFailureLogs", server.server.getClientVersion(), error, config);
131
131
  logger.error("Failed to fetch logs: %s", message);
132
132
  return {
133
133
  content: [
@@ -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): void;
@@ -28,7 +28,7 @@ const LiveArgsSchema = z.object(LiveArgsShape);
28
28
  /**
29
29
  * Launches a desktop browser session
30
30
  */
31
- async function launchDesktopSession(args) {
31
+ async function launchDesktopSession(args, config) {
32
32
  if (!args.desiredBrowser)
33
33
  throw new Error("You must provide a desiredBrowser");
34
34
  if (!args.desiredBrowserVersion)
@@ -40,12 +40,12 @@ async function launchDesktopSession(args) {
40
40
  osVersion: args.desiredOSVersion,
41
41
  browser: args.desiredBrowser,
42
42
  browserVersion: args.desiredBrowserVersion,
43
- });
43
+ }, config);
44
44
  }
45
45
  /**
46
46
  * Launches a mobile browser session
47
47
  */
48
- async function launchMobileSession(args) {
48
+ async function launchMobileSession(args, config) {
49
49
  if (!args.desiredDevice)
50
50
  throw new Error("You must provide a desiredDevice");
51
51
  return startBrowserSession({
@@ -55,18 +55,18 @@ async function launchMobileSession(args) {
55
55
  os: args.desiredOS,
56
56
  osVersion: args.desiredOSVersion,
57
57
  device: args.desiredDevice,
58
- });
58
+ }, config);
59
59
  }
60
60
  /**
61
61
  * Handles the core logic for running a browser session
62
62
  */
63
- async function runBrowserSession(rawArgs) {
63
+ async function runBrowserSession(rawArgs, config) {
64
64
  // Validate and narrow
65
65
  const args = LiveArgsSchema.parse(rawArgs);
66
66
  // Branch desktop vs mobile and delegate
67
67
  const launchUrl = args.platformType === PlatformType.DESKTOP
68
- ? await launchDesktopSession(args)
69
- : await launchMobileSession(args);
68
+ ? await launchDesktopSession(args, config)
69
+ : await launchMobileSession(args, config);
70
70
  return {
71
71
  content: [
72
72
  {
@@ -76,15 +76,15 @@ async function runBrowserSession(rawArgs) {
76
76
  ],
77
77
  };
78
78
  }
79
- export default function addBrowserLiveTools(server) {
79
+ export default function addBrowserLiveTools(server, config) {
80
80
  server.tool("runBrowserLiveSession", "Launch a BrowserStack Live session (desktop or mobile).", LiveArgsShape, async (args) => {
81
81
  try {
82
- trackMCP("runBrowserLiveSession", server.server.getClientVersion());
83
- return await runBrowserSession(args);
82
+ trackMCP("runBrowserLiveSession", server.server.getClientVersion(), undefined, config);
83
+ return await runBrowserSession(args, config);
84
84
  }
85
85
  catch (error) {
86
86
  logger.error("Live session failed: %s", error);
87
- trackMCP("runBrowserLiveSession", server.server.getClientVersion(), error);
87
+ trackMCP("runBrowserLiveSession", server.server.getClientVersion(), error, config);
88
88
  return {
89
89
  content: [
90
90
  {
@@ -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;