@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.
- package/README.md +48 -32
- package/dist/config.d.ts +4 -1
- package/dist/config.js +23 -2
- package/dist/index.d.ts +2 -1
- package/dist/index.js +7 -3
- package/dist/lib/constants.d.ts +3 -0
- package/dist/lib/constants.js +3 -0
- package/dist/lib/instrumentation.d.ts +14 -2
- package/dist/lib/instrumentation.js +104 -29
- package/dist/lib/node-version-notice.d.ts +1 -0
- package/dist/lib/node-version-notice.js +9 -0
- package/dist/lib/tool-latency.d.ts +4 -0
- package/dist/lib/tool-latency.js +16 -0
- package/dist/lib/untrusted-content.d.ts +15 -0
- package/dist/lib/untrusted-content.js +24 -0
- package/dist/lib/upload-validator.d.ts +3 -2
- package/dist/lib/upload-validator.js +18 -15
- package/dist/lib/utils.js +10 -3
- package/dist/lib/version-resolver.d.ts +0 -5
- package/dist/lib/version-resolver.js +8 -2
- package/dist/logger.js +1 -10
- package/dist/server-factory.js +10 -6
- package/dist/tools/accessibility.js +10 -6
- package/dist/tools/accessiblity-utils/accessibility-rag.js +3 -1
- package/dist/tools/accessiblity-utils/auth-config.js +15 -2
- package/dist/tools/appautomate-utils/native-execution/constants.js +4 -2
- package/dist/tools/appautomate.js +3 -1
- package/dist/tools/applive.js +1 -1
- package/dist/tools/ask-browserstack/central-oauth.d.ts +120 -0
- package/dist/tools/ask-browserstack/central-oauth.js +277 -0
- package/dist/tools/ask-browserstack/config.d.ts +102 -0
- package/dist/tools/ask-browserstack/config.js +140 -0
- package/dist/tools/ask-browserstack/egress.d.ts +34 -0
- package/dist/tools/ask-browserstack/egress.js +31 -0
- package/dist/tools/ask-browserstack/register.d.ts +61 -0
- package/dist/tools/ask-browserstack/register.js +416 -0
- package/dist/tools/ask-browserstack/relay.d.ts +201 -0
- package/dist/tools/ask-browserstack/relay.js +577 -0
- package/dist/tools/ask-browserstack/stream.d.ts +116 -0
- package/dist/tools/ask-browserstack/stream.js +236 -0
- package/dist/tools/ask-browserstack/types.d.ts +196 -0
- package/dist/tools/ask-browserstack/types.js +14 -0
- package/dist/tools/automate-utils/list-session-ids.d.ts +28 -0
- package/dist/tools/automate-utils/list-session-ids.js +87 -0
- package/dist/tools/automate-utils/resolve-hashed-build-id.d.ts +30 -0
- package/dist/tools/automate-utils/resolve-hashed-build-id.js +124 -0
- package/dist/tools/automate.d.ts +7 -0
- package/dist/tools/automate.js +104 -1
- package/dist/tools/bstack-sdk.js +2 -2
- package/dist/tools/build-insights.js +41 -2
- package/dist/tools/failurelogs-utils/app-automate.js +4 -3
- package/dist/tools/failurelogs-utils/automate.js +5 -4
- package/dist/tools/failurelogs-utils/resolve-app-build-id.d.ts +2 -0
- package/dist/tools/failurelogs-utils/resolve-app-build-id.js +5 -0
- package/dist/tools/failurelogs-utils/video.d.ts +3 -0
- package/dist/tools/failurelogs-utils/video.js +25 -0
- package/dist/tools/get-failure-logs.js +30 -11
- package/dist/tools/observability.js +3 -1
- package/dist/tools/percy-sdk.js +9 -9
- package/dist/tools/rca-agent-utils/constants.js +2 -2
- package/dist/tools/rca-agent-utils/format-rca.js +5 -4
- package/dist/tools/rca-agent-utils/get-failed-test-id.js +12 -0
- package/dist/tools/rca-agent-utils/types.d.ts +1 -0
- package/dist/tools/rca-agent.js +18 -6
- package/dist/tools/review-agent.js +2 -1
- package/dist/tools/sdk-utils/common/constants.d.ts +1 -1
- package/dist/tools/sdk-utils/common/constants.js +2 -1
- package/dist/tools/sdk-utils/common/utils.js +1 -1
- package/dist/tools/selfheal-utils/fetch-test-code.d.ts +3 -4
- package/dist/tools/selfheal-utils/fetch-test-code.js +30 -57
- package/dist/tools/selfheal.js +28 -35
- package/dist/tools/testmanagement-utils/TCG-utils/api.js +26 -7
- package/dist/tools/testmanagement-utils/TCG-utils/config.d.ts +2 -0
- package/dist/tools/testmanagement-utils/TCG-utils/config.js +5 -0
- package/dist/tools/testmanagement-utils/get-sub-testplan.js +6 -3
- package/dist/tools/testmanagement-utils/get-testplan.js +6 -3
- package/dist/tools/testmanagement-utils/testcase-from-file.js +2 -1
- package/dist/tools/testmanagement-utils/upload-file.js +16 -2
- package/dist/tools/testmanagement.js +39 -17
- package/dist/tools/tfa-rca-collaboration.js +1 -1
- package/dist/tools/tool-handoff.d.ts +66 -0
- package/dist/tools/tool-handoff.js +78 -0
- package/package.json +4 -5
- package/capability/loadtesting.capability-index.json +0 -1791
- package/capability/tm.capability-index.json +0 -19966
- package/dist/tools/capability-registry/bind.d.ts +0 -29
- package/dist/tools/capability-registry/bind.js +0 -134
- package/dist/tools/capability-registry/config.d.ts +0 -62
- package/dist/tools/capability-registry/config.js +0 -218
- package/dist/tools/capability-registry/discovery.d.ts +0 -44
- package/dist/tools/capability-registry/discovery.js +0 -99
- package/dist/tools/capability-registry/egress.d.ts +0 -44
- package/dist/tools/capability-registry/egress.js +0 -128
- package/dist/tools/capability-registry/index-loader.d.ts +0 -133
- package/dist/tools/capability-registry/index-loader.js +0 -369
- package/dist/tools/capability-registry/register.d.ts +0 -34
- package/dist/tools/capability-registry/register.js +0 -373
- package/dist/tools/capability-registry/resolve.d.ts +0 -38
- package/dist/tools/capability-registry/resolve.js +0 -45
- package/dist/tools/capability-registry/search.d.ts +0 -66
- package/dist/tools/capability-registry/search.js +0 -416
- package/dist/tools/capability-registry/types.d.ts +0 -219
- 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 {
|
|
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
|
-
...
|
|
24
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
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("
|
|
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}\
|
|
30
|
+
text: `Observability URL: ${observabilityUrl}\n` +
|
|
31
|
+
wrapUntrusted("observability failure report", `Overview: ${overview}\nError Details: ${details}`),
|
|
30
32
|
},
|
|
31
33
|
],
|
|
32
34
|
};
|
package/dist/tools/percy-sdk.js
CHANGED
|
@@ -38,11 +38,11 @@ export function registerPercyTools(server, config) {
|
|
|
38
38
|
idempotentHint: true,
|
|
39
39
|
}, async (args) => {
|
|
40
40
|
try {
|
|
41
|
-
trackMCP("
|
|
41
|
+
trackMCP("percyVisualTestIntegrationAgent", server.server.getClientVersion(), undefined, config);
|
|
42
42
|
return simulatePercyChangeHandler(args, config);
|
|
43
43
|
}
|
|
44
44
|
catch (error) {
|
|
45
|
-
return handleMCPError("
|
|
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("
|
|
56
|
+
trackMCP("expandPercyVisualTesting", server.server.getClientVersion(), undefined, config);
|
|
57
57
|
return setUpPercyHandler(args, config);
|
|
58
58
|
}
|
|
59
59
|
catch (error) {
|
|
60
|
-
return handleMCPError("
|
|
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("
|
|
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("
|
|
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) {
|
package/dist/tools/rca-agent.js
CHANGED
|
@@ -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
|
-
|
|
29
|
-
|
|
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
|
-
|
|
59
|
-
|
|
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."
|
|
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."
|
|
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
|
|
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: "
|
|
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
|
|
43
|
-
*
|
|
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
|
-
* -
|
|
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
|
|
86
|
-
*
|
|
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
|
-
* -
|
|
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
|
|
111
|
-
"
|
|
112
|
-
"
|
|
113
|
-
"
|
|
114
|
-
|
|
115
|
-
"
|
|
116
|
-
|
|
117
|
-
"
|
|
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
|
|
135
|
-
"API
|
|
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,
|
|
138
|
-
"
|
|
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
|
-
"
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
"
|
|
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
|
-
"
|
|
165
|
-
"
|
|
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
|
-
"
|
|
186
|
-
|
|
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
|
-
"
|
|
202
|
-
|
|
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
|
-
"
|
|
219
|
-
|
|
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");
|