@browserstack/mcp-server 1.5.0-beta.9 → 1.5.1-beta.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 (103) hide show
  1. package/README.md +48 -32
  2. package/dist/config.d.ts +4 -1
  3. package/dist/config.js +23 -2
  4. package/dist/index.d.ts +2 -1
  5. package/dist/index.js +7 -3
  6. package/dist/lib/constants.d.ts +3 -0
  7. package/dist/lib/constants.js +3 -0
  8. package/dist/lib/instrumentation.d.ts +14 -2
  9. package/dist/lib/instrumentation.js +104 -29
  10. package/dist/lib/node-version-notice.d.ts +1 -0
  11. package/dist/lib/node-version-notice.js +9 -0
  12. package/dist/lib/tool-latency.d.ts +4 -0
  13. package/dist/lib/tool-latency.js +16 -0
  14. package/dist/lib/untrusted-content.d.ts +15 -0
  15. package/dist/lib/untrusted-content.js +24 -0
  16. package/dist/lib/upload-validator.d.ts +3 -2
  17. package/dist/lib/upload-validator.js +18 -15
  18. package/dist/lib/utils.js +10 -3
  19. package/dist/lib/version-resolver.d.ts +0 -5
  20. package/dist/lib/version-resolver.js +8 -2
  21. package/dist/logger.js +1 -10
  22. package/dist/server-factory.js +10 -6
  23. package/dist/tools/accessibility.js +10 -6
  24. package/dist/tools/accessiblity-utils/accessibility-rag.js +3 -1
  25. package/dist/tools/accessiblity-utils/auth-config.js +15 -2
  26. package/dist/tools/appautomate-utils/native-execution/constants.js +4 -2
  27. package/dist/tools/appautomate.js +3 -1
  28. package/dist/tools/applive.js +1 -1
  29. package/dist/tools/ask-browserstack/central-oauth.d.ts +120 -0
  30. package/dist/tools/ask-browserstack/central-oauth.js +277 -0
  31. package/dist/tools/ask-browserstack/config.d.ts +102 -0
  32. package/dist/tools/ask-browserstack/config.js +140 -0
  33. package/dist/tools/ask-browserstack/egress.d.ts +34 -0
  34. package/dist/tools/ask-browserstack/egress.js +31 -0
  35. package/dist/tools/ask-browserstack/register.d.ts +61 -0
  36. package/dist/tools/ask-browserstack/register.js +416 -0
  37. package/dist/tools/ask-browserstack/relay.d.ts +201 -0
  38. package/dist/tools/ask-browserstack/relay.js +577 -0
  39. package/dist/tools/ask-browserstack/stream.d.ts +116 -0
  40. package/dist/tools/ask-browserstack/stream.js +236 -0
  41. package/dist/tools/ask-browserstack/types.d.ts +196 -0
  42. package/dist/tools/ask-browserstack/types.js +14 -0
  43. package/dist/tools/automate-utils/list-session-ids.d.ts +28 -0
  44. package/dist/tools/automate-utils/list-session-ids.js +87 -0
  45. package/dist/tools/automate-utils/resolve-hashed-build-id.d.ts +30 -0
  46. package/dist/tools/automate-utils/resolve-hashed-build-id.js +124 -0
  47. package/dist/tools/automate.d.ts +7 -0
  48. package/dist/tools/automate.js +104 -1
  49. package/dist/tools/bstack-sdk.js +2 -2
  50. package/dist/tools/build-insights.js +41 -2
  51. package/dist/tools/failurelogs-utils/app-automate.js +4 -3
  52. package/dist/tools/failurelogs-utils/automate.js +5 -4
  53. package/dist/tools/failurelogs-utils/resolve-app-build-id.d.ts +2 -0
  54. package/dist/tools/failurelogs-utils/resolve-app-build-id.js +5 -0
  55. package/dist/tools/failurelogs-utils/video.d.ts +3 -0
  56. package/dist/tools/failurelogs-utils/video.js +25 -0
  57. package/dist/tools/get-failure-logs.js +30 -11
  58. package/dist/tools/observability.js +3 -1
  59. package/dist/tools/percy-sdk.js +9 -9
  60. package/dist/tools/rca-agent-utils/constants.js +2 -2
  61. package/dist/tools/rca-agent-utils/format-rca.js +5 -4
  62. package/dist/tools/rca-agent-utils/get-failed-test-id.js +12 -0
  63. package/dist/tools/rca-agent-utils/types.d.ts +1 -0
  64. package/dist/tools/rca-agent.js +18 -6
  65. package/dist/tools/review-agent.js +2 -1
  66. package/dist/tools/sdk-utils/common/constants.d.ts +1 -1
  67. package/dist/tools/sdk-utils/common/constants.js +2 -1
  68. package/dist/tools/sdk-utils/common/utils.js +1 -1
  69. package/dist/tools/selfheal-utils/fetch-test-code.d.ts +3 -4
  70. package/dist/tools/selfheal-utils/fetch-test-code.js +30 -57
  71. package/dist/tools/selfheal.js +28 -35
  72. package/dist/tools/testmanagement-utils/TCG-utils/api.js +26 -7
  73. package/dist/tools/testmanagement-utils/TCG-utils/config.d.ts +2 -0
  74. package/dist/tools/testmanagement-utils/TCG-utils/config.js +5 -0
  75. package/dist/tools/testmanagement-utils/get-sub-testplan.js +6 -3
  76. package/dist/tools/testmanagement-utils/get-testplan.js +6 -3
  77. package/dist/tools/testmanagement-utils/testcase-from-file.js +2 -1
  78. package/dist/tools/testmanagement-utils/upload-file.js +16 -2
  79. package/dist/tools/testmanagement.js +39 -17
  80. package/dist/tools/tfa-rca-collaboration.js +1 -1
  81. package/dist/tools/tool-handoff.d.ts +66 -0
  82. package/dist/tools/tool-handoff.js +78 -0
  83. package/package.json +4 -5
  84. package/capability/loadtesting.capability-index.json +0 -1791
  85. package/capability/tm.capability-index.json +0 -19966
  86. package/dist/tools/capability-registry/bind.d.ts +0 -29
  87. package/dist/tools/capability-registry/bind.js +0 -134
  88. package/dist/tools/capability-registry/config.d.ts +0 -62
  89. package/dist/tools/capability-registry/config.js +0 -218
  90. package/dist/tools/capability-registry/discovery.d.ts +0 -44
  91. package/dist/tools/capability-registry/discovery.js +0 -99
  92. package/dist/tools/capability-registry/egress.d.ts +0 -44
  93. package/dist/tools/capability-registry/egress.js +0 -128
  94. package/dist/tools/capability-registry/index-loader.d.ts +0 -133
  95. package/dist/tools/capability-registry/index-loader.js +0 -369
  96. package/dist/tools/capability-registry/register.d.ts +0 -34
  97. package/dist/tools/capability-registry/register.js +0 -373
  98. package/dist/tools/capability-registry/resolve.d.ts +0 -38
  99. package/dist/tools/capability-registry/resolve.js +0 -45
  100. package/dist/tools/capability-registry/search.d.ts +0 -66
  101. package/dist/tools/capability-registry/search.js +0 -416
  102. package/dist/tools/capability-registry/types.d.ts +0 -219
  103. package/dist/tools/capability-registry/types.js +0 -33
@@ -1,8 +1,11 @@
1
1
  import { z } from "zod";
2
2
  import { trackMCP } from "../lib/instrumentation.js";
3
+ import { NEEDS_SESSION_ID } from "./tool-handoff.js";
3
4
  import { retrieveNetworkFailures, retrieveSessionFailures, retrieveConsoleFailures, } from "./failurelogs-utils/automate.js";
4
5
  import { retrieveDeviceLogs, retrieveAppiumLogs, retrieveCrashLogs, } from "./failurelogs-utils/app-automate.js";
5
- import { AppAutomateLogType, AutomateLogType, SessionType, } from "../lib/constants.js";
6
+ import { resolveAppAutomateBuildId } from "./failurelogs-utils/resolve-app-build-id.js";
7
+ import { retrieveSessionVideo } from "./failurelogs-utils/video.js";
8
+ import { AppAutomateLogType, AutomateLogType, SessionType, SessionVideoLogType, } from "../lib/constants.js";
6
9
  // Main log fetcher function
7
10
  export async function getFailureLogs(args, config) {
8
11
  const results = [];
@@ -11,17 +14,16 @@ export async function getFailureLogs(args, config) {
11
14
  if (!args.sessionId) {
12
15
  throw new Error("Session ID is required");
13
16
  }
14
- if (args.sessionType === SessionType.AppAutomate && !args.buildId) {
15
- throw new Error("Build ID is required for app-automate sessions");
16
- }
17
17
  // Validate log types and collect errors
18
18
  validLogTypes = args.logTypes.filter((logType) => {
19
19
  const isAutomate = Object.values(AutomateLogType).includes(logType);
20
20
  const isAppAutomate = Object.values(AppAutomateLogType).includes(logType);
21
21
  if (!isAutomate && !isAppAutomate) {
22
22
  errors.push(`Invalid log type '${logType}'. Valid log types are: ${[
23
- ...Object.values(AutomateLogType),
24
- ...Object.values(AppAutomateLogType),
23
+ ...new Set([
24
+ ...Object.values(AutomateLogType),
25
+ ...Object.values(AppAutomateLogType),
26
+ ]),
25
27
  ].join(", ")}`);
26
28
  return false;
27
29
  }
@@ -46,11 +48,26 @@ export async function getFailureLogs(args, config) {
46
48
  isError: true,
47
49
  };
48
50
  }
51
+ let buildId = args.buildId;
52
+ const needsBuildId = validLogTypes.some((logType) => logType !== SessionVideoLogType);
53
+ if (args.sessionType === SessionType.AppAutomate &&
54
+ needsBuildId &&
55
+ !buildId) {
56
+ buildId = await resolveAppAutomateBuildId(args.sessionId, config);
57
+ if (!buildId) {
58
+ throw new Error("Build ID is required for app-automate sessions");
59
+ }
60
+ }
49
61
  let response;
50
62
  // eslint-disable-next-line no-useless-catch
51
63
  try {
52
64
  for (const logType of validLogTypes) {
53
65
  switch (logType) {
66
+ case SessionVideoLogType: {
67
+ response = await retrieveSessionVideo(args.sessionId, args.sessionType, config);
68
+ results.push({ type: "text", text: response });
69
+ break;
70
+ }
54
71
  case AutomateLogType.NetworkLogs: {
55
72
  response = await retrieveNetworkFailures(args.sessionId, config);
56
73
  results.push({ type: "text", text: response });
@@ -67,17 +84,17 @@ export async function getFailureLogs(args, config) {
67
84
  break;
68
85
  }
69
86
  case AppAutomateLogType.DeviceLogs: {
70
- response = await retrieveDeviceLogs(args.sessionId, args.buildId, config);
87
+ response = await retrieveDeviceLogs(args.sessionId, buildId, config);
71
88
  results.push({ type: "text", text: response });
72
89
  break;
73
90
  }
74
91
  case AppAutomateLogType.AppiumLogs: {
75
- response = await retrieveAppiumLogs(args.sessionId, args.buildId, config);
92
+ response = await retrieveAppiumLogs(args.sessionId, buildId, config);
76
93
  results.push({ type: "text", text: response });
77
94
  break;
78
95
  }
79
96
  case AppAutomateLogType.CrashLogs: {
80
- response = await retrieveCrashLogs(args.sessionId, args.buildId, config);
97
+ response = await retrieveCrashLogs(args.sessionId, buildId, config);
81
98
  results.push({ type: "text", text: response });
82
99
  break;
83
100
  }
@@ -98,7 +115,8 @@ export async function getFailureLogs(args, config) {
98
115
  // Register tool with the MCP server
99
116
  export default function registerGetFailureLogs(server, config) {
100
117
  const tools = {};
101
- tools.getFailureLogs = server.tool("getFailureLogs", "Fetch various types of logs from a BrowserStack session. Supports both automate and app-automate sessions.", {
118
+ tools.getFailureLogs = server.tool("getFailureLogs", "Fetch logs, or the session video URL, for an Automate/App Automate session." +
119
+ NEEDS_SESSION_ID, {
102
120
  sessionType: z
103
121
  .enum([SessionType.Automate, SessionType.AppAutomate])
104
122
  .describe("Type of BrowserStack session. Must be explicitly provided by the user."),
@@ -108,7 +126,7 @@ export default function registerGetFailureLogs(server, config) {
108
126
  buildId: z
109
127
  .string()
110
128
  .optional()
111
- .describe("Required only when sessionType is 'app-automate'. If sessionType is 'app-automate', always ask the user to provide the build ID before proceeding."),
129
+ .describe("App Automate build ID. Optional — resolved from the session if omitted."),
112
130
  logTypes: z
113
131
  .array(z.enum([
114
132
  AutomateLogType.NetworkLogs,
@@ -117,6 +135,7 @@ export default function registerGetFailureLogs(server, config) {
117
135
  AppAutomateLogType.DeviceLogs,
118
136
  AppAutomateLogType.AppiumLogs,
119
137
  AppAutomateLogType.CrashLogs,
138
+ SessionVideoLogType,
120
139
  ]))
121
140
  .describe("The types of logs to fetch."),
122
141
  }, {
@@ -1,3 +1,4 @@
1
+ import { wrapUntrusted } from "../lib/untrusted-content.js";
1
2
  import { z } from "zod";
2
3
  import { getLatestO11YBuildInfo } from "../lib/api.js";
3
4
  import { trackMCP } from "../lib/instrumentation.js";
@@ -26,7 +27,8 @@ export async function getFailuresInLastRun(buildName, projectName, config) {
26
27
  content: [
27
28
  {
28
29
  type: "text",
29
- text: `Observability URL: ${observabilityUrl}\nOverview: ${overview}\nError Details: ${details}`,
30
+ text: `Observability URL: ${observabilityUrl}\n` +
31
+ wrapUntrusted("observability failure report", `Overview: ${overview}\nError Details: ${details}`),
30
32
  },
31
33
  ],
32
34
  };
@@ -38,11 +38,11 @@ export function registerPercyTools(server, config) {
38
38
  idempotentHint: true,
39
39
  }, async (args) => {
40
40
  try {
41
- trackMCP("VisualTestIntegrationAgent", server.server.getClientVersion(), config);
41
+ trackMCP("percyVisualTestIntegrationAgent", server.server.getClientVersion(), undefined, config);
42
42
  return simulatePercyChangeHandler(args, config);
43
43
  }
44
44
  catch (error) {
45
- return handleMCPError("VisualTestIntegrationAgent", server, config, error);
45
+ return handleMCPError("percyVisualTestIntegrationAgent", server, config, error);
46
46
  }
47
47
  });
48
48
  tools.setupPercyVisualTesting = server.tool("expandPercyVisualTesting", SETUP_PERCY_DESCRIPTION, SetUpPercyParamsShape, {
@@ -53,11 +53,11 @@ export function registerPercyTools(server, config) {
53
53
  idempotentHint: true,
54
54
  }, async (args) => {
55
55
  try {
56
- trackMCP("setupPercyVisualTesting", server.server.getClientVersion(), config);
56
+ trackMCP("expandPercyVisualTesting", server.server.getClientVersion(), undefined, config);
57
57
  return setUpPercyHandler(args, config);
58
58
  }
59
59
  catch (error) {
60
- return handleMCPError("setupPercyVisualTesting", server, config, error);
60
+ return handleMCPError("expandPercyVisualTesting", server, config, error);
61
61
  }
62
62
  });
63
63
  tools.addPercySnapshotCommands = server.tool("addPercySnapshotCommands", PERCY_SNAPSHOT_COMMANDS_DESCRIPTION, UpdateTestFileWithInstructionsParams, {
@@ -68,7 +68,7 @@ export function registerPercyTools(server, config) {
68
68
  idempotentHint: true,
69
69
  }, async (args) => {
70
70
  try {
71
- trackMCP("addPercySnapshotCommands", server.server.getClientVersion(), config);
71
+ trackMCP("addPercySnapshotCommands", server.server.getClientVersion(), undefined, config);
72
72
  return await updateTestsWithPercyCommands(args);
73
73
  }
74
74
  catch (error) {
@@ -83,7 +83,7 @@ export function registerPercyTools(server, config) {
83
83
  idempotentHint: true,
84
84
  }, async () => {
85
85
  try {
86
- trackMCP("listTestFiles", server.server.getClientVersion(), config);
86
+ trackMCP("listTestFiles", server.server.getClientVersion(), undefined, config);
87
87
  return addListTestFiles();
88
88
  }
89
89
  catch (error) {
@@ -98,7 +98,7 @@ export function registerPercyTools(server, config) {
98
98
  idempotentHint: true,
99
99
  }, async (args) => {
100
100
  try {
101
- trackMCP("runPercyScan", server.server.getClientVersion(), config);
101
+ trackMCP("runPercyScan", server.server.getClientVersion(), undefined, config);
102
102
  return runPercyScan(args);
103
103
  }
104
104
  catch (error) {
@@ -113,7 +113,7 @@ export function registerPercyTools(server, config) {
113
113
  idempotentHint: true,
114
114
  }, async (args) => {
115
115
  try {
116
- trackMCP("fetchPercyChanges", server.server.getClientVersion(), config);
116
+ trackMCP("fetchPercyChanges", server.server.getClientVersion(), undefined, config);
117
117
  return await fetchPercyChanges(args, config);
118
118
  }
119
119
  catch (error) {
@@ -128,7 +128,7 @@ export function registerPercyTools(server, config) {
128
128
  idempotentHint: true,
129
129
  }, async (args) => {
130
130
  try {
131
- trackMCP("managePercyBuildApproval", server.server.getClientVersion(), config);
131
+ trackMCP("managePercyBuildApproval", server.server.getClientVersion(), undefined, config);
132
132
  return await approveOrDeclinePercyBuild(args, config);
133
133
  }
134
134
  catch (error) {
@@ -13,10 +13,10 @@ export const FETCH_RCA_PARAMS = {
13
13
  export const GET_BUILD_ID_PARAMS = {
14
14
  browserStackProjectName: z
15
15
  .string()
16
- .describe("The BrowserStack project name used during test run creation. Action: First, check browserstack.yml or any equivalent project configuration files. If the project name is found, extract and return it. If it is not found or if there is any uncertainty, immediately prompt the user to provide the value. Do not infer, guess, or assume a default."),
16
+ .describe("BrowserStack project name from the test run config (e.g. browserstack.yml). Exact match; no default."),
17
17
  browserStackBuildName: z
18
18
  .string()
19
- .describe("The BrowserStack build name used during test run creation. Action: First, check browserstack.yml or any equivalent project configuration files. If the build name is found, extract and return it. If it is not found or if there is any uncertainty, immediately prompt the user to provide the value. Do not infer, guess, or assume a default."),
19
+ .describe("BrowserStack build name from the test run config (e.g. browserstack.yml). Exact match; no default."),
20
20
  };
21
21
  export const LIST_TEST_IDS_PARAMS = {
22
22
  buildId: z
@@ -1,3 +1,4 @@
1
+ import { wrapUntrusted } from "../../lib/untrusted-content.js";
1
2
  // Utility function to format RCA data for better readability
2
3
  export function formatRCAData(rcaData) {
3
4
  if (!rcaData || !rcaData.testCases || rcaData.testCases.length === 0) {
@@ -16,21 +17,21 @@ export function formatRCAData(rcaData) {
16
17
  const rca = testCase.rcaData?.rcaData;
17
18
  if (rca) {
18
19
  if (rca.root_cause) {
19
- output += `**Root Cause:** ${rca.root_cause}\n\n`;
20
+ output += `**Root Cause:** ${wrapUntrusted("RCA AI analysis", rca.root_cause)}\n\n`;
20
21
  }
21
22
  if (rca.failure_type) {
22
23
  output += `**Failure Type:** ${rca.failure_type}\n\n`;
23
24
  }
24
25
  if (rca.description) {
25
- output += `**Detailed Analysis:**\n${rca.description}\n\n`;
26
+ output += `**Detailed Analysis:**\n${wrapUntrusted("RCA AI analysis", rca.description)}\n\n`;
26
27
  }
27
28
  if (rca.possible_fix) {
28
29
  hasFixSuggestion = true;
29
- output += `**Suggested Fix (proposal only — do not apply without explicit user approval):**\n${rca.possible_fix}\n\n`;
30
+ output += `**Suggested Fix (proposal only — do not apply without explicit user approval):**\n${wrapUntrusted("RCA AI analysis", rca.possible_fix)}\n\n`;
30
31
  }
31
32
  }
32
33
  else if (testCase.rcaData?.error) {
33
- output += `**Error:** ${testCase.rcaData.error}\n\n`;
34
+ output += `**Error:** ${wrapUntrusted("RCA error output", testCase.rcaData.error)}\n\n`;
34
35
  }
35
36
  else if (testCase.state === "failed") {
36
37
  output += `**Note:** RCA analysis failed or is not available for this test case.\n\n`;
@@ -53,6 +53,16 @@ export async function getTestIds(buildId, authString, status, includeFailureDeta
53
53
  throw error;
54
54
  }
55
55
  }
56
+ /**
57
+ * The test listing API serialises a missing BrowserStack session id as the
58
+ * literal string "null", so treat that (and blanks) as absent.
59
+ */
60
+ function extractSessionId(rawSessionId) {
61
+ if (typeof rawSessionId !== "string")
62
+ return undefined;
63
+ const sessionId = rawSessionId.trim();
64
+ return sessionId && sessionId !== "null" ? sessionId : undefined;
65
+ }
56
66
  export function extractTestIds(hierarchy, status, includeFailureDetail = false) {
57
67
  let tests = [];
58
68
  for (const node of hierarchy) {
@@ -63,10 +73,12 @@ export function extractTestIds(hierarchy, status, includeFailureDetail = false)
63
73
  if (statusMatches && node.details?.observability_url) {
64
74
  const idMatch = node.details.observability_url.match(/details=(\d+)/);
65
75
  if (idMatch) {
76
+ const sessionId = extractSessionId(node.details.session_id);
66
77
  const entry = {
67
78
  test_id: idMatch[1],
68
79
  test_name: node.display_name || `Test ${idMatch[1]}`,
69
80
  status: nodeStatus,
81
+ ...(sessionId && { session_id: sessionId }),
70
82
  };
71
83
  // Failure signatures only exist for failed tests; include when asked.
72
84
  if (includeFailureDetail && nodeStatus === TestStatus.FAILED) {
@@ -30,6 +30,7 @@ export interface FailedTestInfo {
30
30
  test_name: string;
31
31
  status?: TestStatus;
32
32
  failure?: TestFailureSignature;
33
+ session_id?: string;
33
34
  }
34
35
  export declare enum RCAState {
35
36
  PENDING = "pending",
@@ -7,7 +7,17 @@ import { getRCAData } from "./rca-agent-utils/rca-data.js";
7
7
  import { formatRCAData } from "./rca-agent-utils/format-rca.js";
8
8
  import { handleMCPError } from "../lib/utils.js";
9
9
  import { trackMCP } from "../index.js";
10
+ import { NEEDS_BUILD_ID, NEEDS_TEST_IDS } from "./tool-handoff.js";
10
11
  import { FETCH_RCA_PARAMS, GET_BUILD_ID_PARAMS, LIST_TEST_IDS_PARAMS, } from "./rca-agent-utils/constants.js";
12
+ function describeBuildIdError(error, args) {
13
+ const status = error?.response
14
+ ?.status;
15
+ const message = error instanceof Error ? error.message : "Unknown error";
16
+ if (status === 404 || /\b404\b/.test(message)) {
17
+ return `No build found for project "${args.browserStackProjectName}" and build "${args.browserStackBuildName}". Both names must match the BrowserStack Automate dashboard exactly.`;
18
+ }
19
+ return message;
20
+ }
11
21
  // Tool function to fetch build ID
12
22
  export async function getBuildIdTool(args, config) {
13
23
  try {
@@ -25,8 +35,8 @@ export async function getBuildIdTool(args, config) {
25
35
  };
26
36
  }
27
37
  catch (error) {
28
- logger.error("Error fetching build ID", error);
29
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
38
+ const errorMessage = describeBuildIdError(error, args);
39
+ logger.error("Error fetching build ID: %s", errorMessage);
30
40
  return {
31
41
  content: [
32
42
  {
@@ -55,8 +65,8 @@ export async function listBuildIdTool(args, config) {
55
65
  };
56
66
  }
57
67
  catch (error) {
58
- logger.error("Error fetching build ID", error);
59
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
68
+ const errorMessage = describeBuildIdError(error, args);
69
+ logger.error("Error fetching build ID: %s", errorMessage);
60
70
  return {
61
71
  content: [
62
72
  {
@@ -130,7 +140,8 @@ export async function listTestIdsTool(args, config) {
130
140
  }
131
141
  export default function addRCATools(server, config) {
132
142
  const tools = {};
133
- tools.fetchRCA = server.tool("fetchRCA", "Fetch AI Root Cause Analysis for the current user's failed BrowserStack Automate/App-Automate tests. Suggests fixes only; never auto-apply, require explicit user approval.", FETCH_RCA_PARAMS, {
143
+ tools.fetchRCA = server.tool("fetchRCA", "Fetch AI Root Cause Analysis for the current user's failed BrowserStack Automate/App-Automate tests. Suggests fixes only; never auto-apply, require explicit user approval." +
144
+ NEEDS_TEST_IDS, FETCH_RCA_PARAMS, {
134
145
  title: "Fetch Root Cause Analysis",
135
146
  readOnlyHint: true,
136
147
  openWorldHint: false,
@@ -175,7 +186,8 @@ export default function addRCATools(server, config) {
175
186
  return handleMCPError("listBuildId", server, config, error);
176
187
  }
177
188
  });
178
- tools.listTestIds = server.tool("listTestIds", "List all tests of a BrowserStack build (each with its status); optional status filter.", LIST_TEST_IDS_PARAMS, {
189
+ tools.listTestIds = server.tool("listTestIds", "List all tests of a BrowserStack build (each with its status and session id); optional status filter." +
190
+ NEEDS_BUILD_ID, LIST_TEST_IDS_PARAMS, {
179
191
  title: "List Test IDs",
180
192
  readOnlyHint: true,
181
193
  openWorldHint: false,
@@ -1,3 +1,4 @@
1
+ import { wrapUntrusted } from "../lib/untrusted-content.js";
1
2
  import { getBrowserStackAuth } from "../lib/get-auth.js";
2
3
  import { getPercyBuildCount } from "./review-agent-utils/build-counts.js";
3
4
  import { getChangedPercySnapshotIds } from "./review-agent-utils/percy-snapshots.js";
@@ -50,7 +51,7 @@ export async function fetchPercyChanges(args, config) {
50
51
  return {
51
52
  content: allDiffs.map((diff) => ({
52
53
  type: "text",
53
- text: `${diff.name} → ${diff.title}: ${diff.description ?? ""}`,
54
+ text: wrapUntrusted("Percy AI visual-diff description", `${diff.name} → ${diff.title}: ${diff.description ?? ""}`),
54
55
  })),
55
56
  };
56
57
  }
@@ -1,4 +1,4 @@
1
- export declare const IMPORTANT_SETUP_WARNING = "IMPORTANT: DO NOT SKIP ANY STEP. All the setup steps described below MUST be executed regardless of any existing configuration or setup. This ensures proper BrowserStack SDK setup.";
1
+ export declare const IMPORTANT_SETUP_WARNING: string;
2
2
  export declare const SETUP_PERCY_DESCRIPTION = "Set up or expand Percy visual testing configuration with comprehensive coverage for existing projects that might have Percy integrated. This supports both Percy Web Standalone and Percy Automate. Example prompts: Expand percy coverage for this project {project_name}";
3
3
  export declare const LIST_TEST_FILES_DESCRIPTION = "Lists all test files for a given set of directories.";
4
4
  export declare const PERCY_SNAPSHOT_COMMANDS_DESCRIPTION = "Adds Percy snapshot commands to the specified test files.";
@@ -1,4 +1,5 @@
1
- export const IMPORTANT_SETUP_WARNING = "IMPORTANT: DO NOT SKIP ANY STEP. All the setup steps described below MUST be executed regardless of any existing configuration or setup. This ensures proper BrowserStack SDK setup.";
1
+ export const IMPORTANT_SETUP_WARNING = "IMPORTANT: DO NOT SKIP ANY STEP. All the setup steps described below MUST be executed regardless of any existing configuration or setup. This ensures proper BrowserStack SDK setup. " +
2
+ "If you cannot run commands or edit files in the user's project (e.g. a chat-only client), present every step below to the user in full — including each shell command, the package.json changes, and the complete browserstack.yml contents — instead of summarizing them.";
2
3
  export const SETUP_PERCY_DESCRIPTION = "Set up or expand Percy visual testing configuration with comprehensive coverage for existing projects that might have Percy integrated. This supports both Percy Web Standalone and Percy Automate. Example prompts: Expand percy coverage for this project {project_name}";
3
4
  export const LIST_TEST_FILES_DESCRIPTION = "Lists all test files for a given set of directories.";
4
5
  export const PERCY_SNAPSHOT_COMMANDS_DESCRIPTION = "Adds Percy snapshot commands to the specified test files.";
@@ -51,7 +51,7 @@ export async function formatToolResult(resultPromise, runType) {
51
51
  if (runType != "percy-web") {
52
52
  finalContent.push({
53
53
  type: "text",
54
- text: "CRITICAL: After the build is run, you MUST explicitly ask the user if they want to perform RCA (Root Cause Analysis) for any failed tests using the RCA tool. This step is mandatory and should not be skipped.",
54
+ text: "After the build has run, root cause analysis (RCA) is available for any failed tests: getBuildId, then listTestIds, then fetchRCA.",
55
55
  });
56
56
  }
57
57
  return {
@@ -39,11 +39,10 @@ export interface SessionTestCode {
39
39
  */
40
40
  export declare function fetchTestCodeBySession(sessionId: string, config: BrowserStackConfig): Promise<SessionTestCode>;
41
41
  /**
42
- * Builds a directive guidance block for the calling LLM when one or more
43
- * sessions did not return usable test code. Each status block contains:
42
+ * Builds per-status notes when one or more sessions did not return usable
43
+ * test code. Each status block contains:
44
44
  * - A diagnosis (what happened, per BrowserStack)
45
- * - An explicit "do NOT paraphrase this as X" constraint
46
- * - A ready-to-say phrasing the LLM can quote when replying to the user
45
+ * - The next step
47
46
  *
48
47
  * Returns empty string when every session returned `status: ok`.
49
48
  */
@@ -82,11 +82,10 @@ export async function fetchTestCodeBySession(sessionId, config) {
82
82
  }
83
83
  }
84
84
  /**
85
- * Builds a directive guidance block for the calling LLM when one or more
86
- * sessions did not return usable test code. Each status block contains:
85
+ * Builds per-status notes when one or more sessions did not return usable
86
+ * test code. Each status block contains:
87
87
  * - A diagnosis (what happened, per BrowserStack)
88
- * - An explicit "do NOT paraphrase this as X" constraint
89
- * - A ready-to-say phrasing the LLM can quote when replying to the user
88
+ * - The next step
90
89
  *
91
90
  * Returns empty string when every session returned `status: ok`.
92
91
  */
@@ -107,21 +106,14 @@ export function describeTestCodeFetchIssues(sessionTestCodes) {
107
106
  `### Non-SDK build — session(s): ${ids}`,
108
107
  "",
109
108
  "Diagnosis: BrowserStack's test-code API returned HTTP 200 with rows " +
110
- "where `code` and `filename` are null. This is the KNOWN signature " +
111
- "of a BrowserStack run that was NOT instrumented with the " +
112
- "BrowserStack SDK / Observability. The API literally has no source " +
113
- "code to return for these sessions — this is by design.",
114
- "",
115
- "DO NOT tell the user any of the following (all are wrong for this " +
116
- "status): 'credentials issue', 'credentials or session issue', " +
117
- "'could not fetch from the API due to auth', '401', 'unauthorized'. " +
118
- "The credentials are fine; the build just isn't SDK-enabled.",
119
- "",
120
- "Say this (or very close to it) to the user, then wait for their reply:",
121
- ' "This is a non-SDK BrowserStack build, so the test-code API has ' +
122
- "no source to return for it (this is expected, not a credentials " +
123
- "problem). Can you tell me the local file path where these tests live? " +
124
- "Once I have the file, I'll apply the healed locators listed in the plan.\"",
109
+ "where `code` and `filename` are null. This is the signature of a " +
110
+ "BrowserStack run that was not instrumented with the BrowserStack " +
111
+ "SDK / Observability, so the API has no source code to return for " +
112
+ "these sessions. Credentials are not involved; this is not an auth " +
113
+ "failure or a session problem.",
114
+ "",
115
+ "Next step: the healed locators can be applied once the user shares " +
116
+ "the local file path where these tests live.",
125
117
  ].join("\n"));
126
118
  }
127
119
  const unauthorized = byStatus.get("unauthorized");
@@ -131,23 +123,16 @@ export function describeTestCodeFetchIssues(sessionTestCodes) {
131
123
  `### Unauthorized (HTTP 401) — session(s): ${ids}`,
132
124
  "",
133
125
  "Diagnosis: BrowserStack's test-code API rejected the credentials " +
134
- "(HTTP 401). This IS an authentication problem for this specific " +
135
- "API — the healing report endpoint and test-code endpoint use the " +
126
+ "(HTTP 401). This is an authentication problem for this specific " +
127
+ "API. The healing report endpoint and test-code endpoint use the " +
136
128
  "same BrowserStack auth, so if the report fetch above succeeded " +
137
- "with the same creds, suspect that the access key configured on " +
138
- "the MCP server was rotated.",
139
- "",
140
- "DO NOT say 'credentials or session issue' (that hides the real cause). " +
141
- "Name the 401 explicitly and offer the user a choice. Do NOT ask " +
142
- "the user to paste a BrowserStack username or access key in chat — " +
143
- "the MCP server reads credentials from its own environment.",
129
+ "with the same creds, the access key configured on the MCP server " +
130
+ "was likely rotated.",
144
131
  "",
145
- "Say this (or very close to it) to the user, then wait for their reply:",
146
- ' "The BrowserStack test-code API returned 401 Unauthorized for ' +
147
- `session(s) ${ids}. Would you like to: (a) update the BrowserStack ` +
148
- "username and access key on the MCP server (BROWSERSTACK_USERNAME / " +
149
- "BROWSERSTACK_ACCESS_KEY) and restart it, or (b) skip the API and " +
150
- 'point me at the local test file so I can apply the healed locators there?"',
132
+ "Next step: either update BROWSERSTACK_USERNAME / " +
133
+ "BROWSERSTACK_ACCESS_KEY on the MCP server and restart it, or apply " +
134
+ "the healed locators to the local test file if the user shares its " +
135
+ "path. Credentials are read from the server environment, not from chat.",
151
136
  ].join("\n"));
152
137
  }
153
138
  const forbidden = byStatus.get("forbidden");
@@ -159,16 +144,10 @@ export function describeTestCodeFetchIssues(sessionTestCodes) {
159
144
  "Diagnosis: BrowserStack's test-code API accepted the credentials " +
160
145
  "but denied access to the session's source (HTTP 403). Typically " +
161
146
  "the user does not own this session, or the account does not have " +
162
- "Observability enabled.",
147
+ "Observability enabled. The credentials themselves were accepted.",
163
148
  "",
164
- "Do NOT blame the credentials broadly — say 'access denied for this " +
165
- "session' and move on.",
166
- "",
167
- "Say this (or very close to it) to the user:",
168
- ` "BrowserStack denied access (HTTP 403) to session(s) ${ids}. ` +
169
- "This usually means the account does not own these sessions or " +
170
- "does not have Observability enabled. Can you either share the " +
171
- 'local test file path, or confirm which account these sessions belong to?"',
149
+ "Next step: confirm which account owns these sessions, or apply the " +
150
+ "healed locators to the local test file if the user shares its path.",
172
151
  ].join("\n"));
173
152
  }
174
153
  const notFound = byStatus.get("not_found");
@@ -178,14 +157,11 @@ export function describeTestCodeFetchIssues(sessionTestCodes) {
178
157
  `### Not found (HTTP 404) — session(s): ${ids}`,
179
158
  "",
180
159
  "Diagnosis: BrowserStack returned HTTP 404 — the session id is most " +
181
- "likely wrong, or the session has been purged.",
182
- "",
183
- "Do NOT say 'credentials'. Ask the user to verify the id.",
160
+ "likely wrong, or the session has been purged. Credentials are not " +
161
+ "involved.",
184
162
  "",
185
- "Say this (or very close to it) to the user:",
186
- ` "I couldn't find session(s) ${ids} on BrowserStack (HTTP 404). ` +
187
- "Can you double-check the session id(s), or share the local test " +
188
- 'file directly so I can apply the healed locators?"',
163
+ "Next step: verify the session id(s), or apply the healed locators to " +
164
+ "the local test file if the user shares its path.",
189
165
  ].join("\n"));
190
166
  }
191
167
  const empty = byStatus.get("empty");
@@ -198,9 +174,8 @@ export function describeTestCodeFetchIssues(sessionTestCodes) {
198
174
  "no test code was captured (e.g. a raw Selenium session not linked " +
199
175
  "to a test framework).",
200
176
  "",
201
- "Say this (or very close to it) to the user:",
202
- ` "BrowserStack has no test code recorded for session(s) ${ids}. ` +
203
- 'Can you point me at the local test file where these locators live?"',
177
+ "Next step: the healed locators can be applied once the user shares " +
178
+ "the local test file where these locators live.",
204
179
  ].join("\n"));
205
180
  }
206
181
  const errored = byStatus.get("error");
@@ -215,10 +190,8 @@ export function describeTestCodeFetchIssues(sessionTestCodes) {
215
190
  `Diagnosis: network/transport failure — ${details}. This is not an ` +
216
191
  "auth issue.",
217
192
  "",
218
- "Say this (or very close to it) to the user:",
219
- ` "I hit a transport error fetching test code from BrowserStack ` +
220
- `(${details}). Want me to retry, or would you prefer to share the ` +
221
- 'local test file directly?"',
193
+ "Next step: retry the fetch, or apply the healed locators to the " +
194
+ "local test file if the user shares its path.",
222
195
  ].join("\n"));
223
196
  }
224
197
  return notes.join("\n\n");