@browserstack/mcp-server 1.4.0-beta.3 → 1.4.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 +45 -29
- package/dist/lib/constants.d.ts +3 -0
- package/dist/lib/constants.js +3 -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/version-resolver.d.ts +0 -5
- package/dist/lib/version-resolver.js +8 -2
- package/dist/logger.js +1 -10
- package/dist/tools/accessibility.js +4 -3
- package/dist/tools/accessiblity-utils/accessibility-rag.js +3 -1
- 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/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 +28 -11
- package/dist/tools/observability.js +3 -1
- package/dist/tools/percy-sdk.js +9 -9
- 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 +1 -1
- 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/selfheal.js +1 -1
- 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 +2 -2
- package/dist/tools/tool-handoff.d.ts +5 -1
- package/dist/tools/tool-handoff.js +7 -4
- package/package.json +1 -1
|
@@ -3,7 +3,9 @@ import { trackMCP } from "../lib/instrumentation.js";
|
|
|
3
3
|
import { NEEDS_SESSION_ID } from "./tool-handoff.js";
|
|
4
4
|
import { retrieveNetworkFailures, retrieveSessionFailures, retrieveConsoleFailures, } from "./failurelogs-utils/automate.js";
|
|
5
5
|
import { retrieveDeviceLogs, retrieveAppiumLogs, retrieveCrashLogs, } from "./failurelogs-utils/app-automate.js";
|
|
6
|
-
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";
|
|
7
9
|
// Main log fetcher function
|
|
8
10
|
export async function getFailureLogs(args, config) {
|
|
9
11
|
const results = [];
|
|
@@ -12,17 +14,16 @@ export async function getFailureLogs(args, config) {
|
|
|
12
14
|
if (!args.sessionId) {
|
|
13
15
|
throw new Error("Session ID is required");
|
|
14
16
|
}
|
|
15
|
-
if (args.sessionType === SessionType.AppAutomate && !args.buildId) {
|
|
16
|
-
throw new Error("Build ID is required for app-automate sessions");
|
|
17
|
-
}
|
|
18
17
|
// Validate log types and collect errors
|
|
19
18
|
validLogTypes = args.logTypes.filter((logType) => {
|
|
20
19
|
const isAutomate = Object.values(AutomateLogType).includes(logType);
|
|
21
20
|
const isAppAutomate = Object.values(AppAutomateLogType).includes(logType);
|
|
22
21
|
if (!isAutomate && !isAppAutomate) {
|
|
23
22
|
errors.push(`Invalid log type '${logType}'. Valid log types are: ${[
|
|
24
|
-
...
|
|
25
|
-
|
|
23
|
+
...new Set([
|
|
24
|
+
...Object.values(AutomateLogType),
|
|
25
|
+
...Object.values(AppAutomateLogType),
|
|
26
|
+
]),
|
|
26
27
|
].join(", ")}`);
|
|
27
28
|
return false;
|
|
28
29
|
}
|
|
@@ -47,11 +48,26 @@ export async function getFailureLogs(args, config) {
|
|
|
47
48
|
isError: true,
|
|
48
49
|
};
|
|
49
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
|
+
}
|
|
50
61
|
let response;
|
|
51
62
|
// eslint-disable-next-line no-useless-catch
|
|
52
63
|
try {
|
|
53
64
|
for (const logType of validLogTypes) {
|
|
54
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
|
+
}
|
|
55
71
|
case AutomateLogType.NetworkLogs: {
|
|
56
72
|
response = await retrieveNetworkFailures(args.sessionId, config);
|
|
57
73
|
results.push({ type: "text", text: response });
|
|
@@ -68,17 +84,17 @@ export async function getFailureLogs(args, config) {
|
|
|
68
84
|
break;
|
|
69
85
|
}
|
|
70
86
|
case AppAutomateLogType.DeviceLogs: {
|
|
71
|
-
response = await retrieveDeviceLogs(args.sessionId,
|
|
87
|
+
response = await retrieveDeviceLogs(args.sessionId, buildId, config);
|
|
72
88
|
results.push({ type: "text", text: response });
|
|
73
89
|
break;
|
|
74
90
|
}
|
|
75
91
|
case AppAutomateLogType.AppiumLogs: {
|
|
76
|
-
response = await retrieveAppiumLogs(args.sessionId,
|
|
92
|
+
response = await retrieveAppiumLogs(args.sessionId, buildId, config);
|
|
77
93
|
results.push({ type: "text", text: response });
|
|
78
94
|
break;
|
|
79
95
|
}
|
|
80
96
|
case AppAutomateLogType.CrashLogs: {
|
|
81
|
-
response = await retrieveCrashLogs(args.sessionId,
|
|
97
|
+
response = await retrieveCrashLogs(args.sessionId, buildId, config);
|
|
82
98
|
results.push({ type: "text", text: response });
|
|
83
99
|
break;
|
|
84
100
|
}
|
|
@@ -99,7 +115,7 @@ export async function getFailureLogs(args, config) {
|
|
|
99
115
|
// Register tool with the MCP server
|
|
100
116
|
export default function registerGetFailureLogs(server, config) {
|
|
101
117
|
const tools = {};
|
|
102
|
-
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." +
|
|
103
119
|
NEEDS_SESSION_ID, {
|
|
104
120
|
sessionType: z
|
|
105
121
|
.enum([SessionType.Automate, SessionType.AppAutomate])
|
|
@@ -110,7 +126,7 @@ export default function registerGetFailureLogs(server, config) {
|
|
|
110
126
|
buildId: z
|
|
111
127
|
.string()
|
|
112
128
|
.optional()
|
|
113
|
-
.describe("
|
|
129
|
+
.describe("App Automate build ID. Optional — resolved from the session if omitted."),
|
|
114
130
|
logTypes: z
|
|
115
131
|
.array(z.enum([
|
|
116
132
|
AutomateLogType.NetworkLogs,
|
|
@@ -119,6 +135,7 @@ export default function registerGetFailureLogs(server, config) {
|
|
|
119
135
|
AppAutomateLogType.DeviceLogs,
|
|
120
136
|
AppAutomateLogType.AppiumLogs,
|
|
121
137
|
AppAutomateLogType.CrashLogs,
|
|
138
|
+
SessionVideoLogType,
|
|
122
139
|
]))
|
|
123
140
|
.describe("The types of logs to fetch."),
|
|
124
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) {
|
|
@@ -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
|
@@ -177,7 +177,7 @@ export default function addRCATools(server, config) {
|
|
|
177
177
|
return handleMCPError("listBuildId", server, config, error);
|
|
178
178
|
}
|
|
179
179
|
});
|
|
180
|
-
tools.listTestIds = server.tool("listTestIds", "List all tests of a BrowserStack build (each with its status); optional status filter." +
|
|
180
|
+
tools.listTestIds = server.tool("listTestIds", "List all tests of a BrowserStack build (each with its status and session id); optional status filter." +
|
|
181
181
|
NEEDS_BUILD_ID, LIST_TEST_IDS_PARAMS, {
|
|
182
182
|
title: "List Test IDs",
|
|
183
183
|
readOnlyHint: true,
|
|
@@ -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.";
|
package/dist/tools/selfheal.js
CHANGED
|
@@ -506,7 +506,7 @@ export default function addSelfHealTools(server, config) {
|
|
|
506
506
|
sessions: sessionsFieldSchema.describe("Sessions to plan edits for. See tool description for accepted shapes."),
|
|
507
507
|
}, {
|
|
508
508
|
title: "Prepare Self-Healing Plan",
|
|
509
|
-
readOnlyHint:
|
|
509
|
+
readOnlyHint: false,
|
|
510
510
|
openWorldHint: false,
|
|
511
511
|
destructiveHint: false,
|
|
512
512
|
idempotentHint: true,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { wrapUntrusted } from "../../lib/untrusted-content.js";
|
|
1
2
|
import { fetchFormFields, triggerTestCaseGeneration, pollScenariosTestDetails, bulkCreateTestCases, } from "./TCG-utils/api.js";
|
|
2
3
|
import { buildDefaultFieldMaps, findBooleanFieldId, } from "./TCG-utils/helpers.js";
|
|
3
4
|
import { signedUrlMap } from "../../lib/inmemory-store.js";
|
|
@@ -37,7 +38,7 @@ export async function createTestCasesFromFile(args, context, config) {
|
|
|
37
38
|
content: [
|
|
38
39
|
{
|
|
39
40
|
type: "text",
|
|
40
|
-
text: resultString,
|
|
41
|
+
text: wrapUntrusted("AI-generated test cases from the uploaded document", resultString),
|
|
41
42
|
},
|
|
42
43
|
{
|
|
43
44
|
type: "text",
|
|
@@ -19,16 +19,30 @@ export const UploadFileSchema = z.object({
|
|
|
19
19
|
.describe("ID of the project where the file should be uploaded. Do not assume it, always ask user for it."),
|
|
20
20
|
file_path: z
|
|
21
21
|
.string()
|
|
22
|
-
.describe("Full path to the file that should be uploaded"
|
|
22
|
+
.describe("Full path to the file that should be uploaded. Must be inside the " +
|
|
23
|
+
"directory configured via the MCP_UPLOAD_BASE_DIR environment variable."),
|
|
23
24
|
});
|
|
24
25
|
/**
|
|
25
26
|
* Uploads a file to BrowserStack Test Management and returns the signed URL.
|
|
26
27
|
*/
|
|
27
28
|
export async function uploadFile(args, config) {
|
|
28
29
|
const { project_identifier, file_path } = args;
|
|
30
|
+
if (!appConfig.UPLOAD_BASE_DIR) {
|
|
31
|
+
return {
|
|
32
|
+
content: [
|
|
33
|
+
{
|
|
34
|
+
type: "text",
|
|
35
|
+
text: "File upload is disabled. Set the MCP_UPLOAD_BASE_DIR environment " +
|
|
36
|
+
"variable to a directory that contains the files you want to upload, " +
|
|
37
|
+
"then restart the MCP server. Uploads are restricted to that directory.",
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
isError: true,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
29
43
|
try {
|
|
30
44
|
// Canonicalize path and enforce upload safety rules (extension, size,
|
|
31
|
-
// hidden-directory traversal,
|
|
45
|
+
// hidden-directory traversal, base-dir containment).
|
|
32
46
|
const safePath = validateUploadPath(file_path, {
|
|
33
47
|
allowedExtensions: TEST_MANAGEMENT_ATTACHMENT_EXTENSIONS,
|
|
34
48
|
maxSizeBytes: MAX_ATTACHMENT_UPLOAD_BYTES,
|
|
@@ -264,11 +264,11 @@ export async function uploadProductRequirementFileTool(args, config, server) {
|
|
|
264
264
|
*/
|
|
265
265
|
export async function createTestCasesFromFileTool(args, context, config, server) {
|
|
266
266
|
try {
|
|
267
|
-
trackMCP("createTestCasesFromFile", server.server.getClientVersion(), undefined);
|
|
267
|
+
trackMCP("createTestCasesFromFile", server.server.getClientVersion(), undefined, config);
|
|
268
268
|
return await createTestCasesFromFile(args, context, config);
|
|
269
269
|
}
|
|
270
270
|
catch (err) {
|
|
271
|
-
trackMCP("createTestCasesFromFile", server.server.getClientVersion(), err);
|
|
271
|
+
trackMCP("createTestCasesFromFile", server.server.getClientVersion(), err, config);
|
|
272
272
|
return {
|
|
273
273
|
content: [
|
|
274
274
|
{
|
|
@@ -52,7 +52,11 @@ export declare const NEEDS_TEST_PLAN_ID: string;
|
|
|
52
52
|
export declare const PLAN_WRITES_VIA_AGENT: string;
|
|
53
53
|
/** A build id comes from either build-lookup tool. */
|
|
54
54
|
export declare const NEEDS_BUILD_ID: string;
|
|
55
|
-
/**
|
|
55
|
+
/**
|
|
56
|
+
* Session ids ARE listable now — PR #395 added `listSessions`, which merged into main while
|
|
57
|
+
* this branch was open. This used to send the model to askBrowserStackAI for want of a tool;
|
|
58
|
+
* pointing at the agent when a real tool exists is exactly what the header above forbids.
|
|
59
|
+
*/
|
|
56
60
|
export declare const NEEDS_SESSION_ID: string;
|
|
57
61
|
/** A completed scan's ids come from startAccessibilityScan, or from the agent. */
|
|
58
62
|
export declare const NEEDS_A11Y_SCAN_ID: string;
|
|
@@ -60,10 +60,13 @@ export const PLAN_WRITES_VIA_AGENT = " Creating a test plan or sub-plan, and lin
|
|
|
60
60
|
"want. It asks you to confirm before changing anything.";
|
|
61
61
|
/** A build id comes from either build-lookup tool. */
|
|
62
62
|
export const NEEDS_BUILD_ID = needsIdFrom("a BrowserStack build id", "getBuildId or listBuildId");
|
|
63
|
-
/**
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
63
|
+
/**
|
|
64
|
+
* Session ids ARE listable now — PR #395 added `listSessions`, which merged into main while
|
|
65
|
+
* this branch was open. This used to send the model to askBrowserStackAI for want of a tool;
|
|
66
|
+
* pointing at the agent when a real tool exists is exactly what the header above forbids.
|
|
67
|
+
*/
|
|
68
|
+
export const NEEDS_SESSION_ID = " Requires a session id. listSessions lists them for a build; if you do not have the " +
|
|
69
|
+
"build either, getBuildId or listBuildId resolves one from a project and build name.";
|
|
67
70
|
/** A completed scan's ids come from startAccessibilityScan, or from the agent. */
|
|
68
71
|
export const NEEDS_A11Y_SCAN_ID = " Requires the ids of a completed scan, which are returned by startAccessibilityScan. " +
|
|
69
72
|
"No tool here lists past scans, so if you do not have the ids, start a new scan rather " +
|