@browserstack/mcp-server 1.5.0 → 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/dist/tools/accessibility.js +1 -1
- package/dist/tools/accessiblity-utils/auth-config.js +15 -2
- package/dist/tools/rca-agent-utils/constants.js +2 -2
- package/dist/tools/rca-agent.js +13 -4
- 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 +22 -32
- package/dist/tools/testmanagement-utils/get-sub-testplan.js +6 -3
- package/dist/tools/testmanagement-utils/get-testplan.js +6 -3
- package/dist/tools/tfa-rca-collaboration.js +1 -1
- package/package.json +1 -1
|
@@ -211,7 +211,7 @@ async function runAccessibilityScan(name, pageURL, context, config, authConfigId
|
|
|
211
211
|
}
|
|
212
212
|
export default function addAccessibilityTools(server, config) {
|
|
213
213
|
const tools = {};
|
|
214
|
-
tools.accessibilityExpert = server.tool("accessibilityExpert", "
|
|
214
|
+
tools.accessibilityExpert = server.tool("accessibilityExpert", "Answer accessibility, a11y, and WCAG questions using BrowserStack's accessibility knowledge base.", {
|
|
215
215
|
query: z
|
|
216
216
|
.string()
|
|
217
217
|
.describe("Any accessibility, a11y, WCAG, or web accessibility question"),
|
|
@@ -118,8 +118,9 @@ export class AccessibilityAuthConfig {
|
|
|
118
118
|
throw new Error("BrowserStack credentials are not set for AccessibilityAuthConfig.");
|
|
119
119
|
}
|
|
120
120
|
try {
|
|
121
|
+
// No GET-by-id route exists; list and select.
|
|
121
122
|
const response = await apiClient.get({
|
|
122
|
-
url:
|
|
123
|
+
url: "https://api-accessibility.browserstack.com/api/website-scanner/v1/auth_configs",
|
|
123
124
|
headers: {
|
|
124
125
|
Authorization: "Basic " +
|
|
125
126
|
Buffer.from(`${this.auth.username}:${this.auth.password}`).toString("base64"),
|
|
@@ -129,7 +130,19 @@ export class AccessibilityAuthConfig {
|
|
|
129
130
|
if (!data.success) {
|
|
130
131
|
throw new Error(`Unable to get auth config: ${data.errors?.join(", ")}`);
|
|
131
132
|
}
|
|
132
|
-
|
|
133
|
+
const match = data.data?.authConfigs?.find((entry) => Number(entry.id) === Number(configId));
|
|
134
|
+
if (!match) {
|
|
135
|
+
throw new Error(`Auth config ${configId} not found for this account`);
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
success: true,
|
|
139
|
+
data: {
|
|
140
|
+
id: match.id,
|
|
141
|
+
name: match.name,
|
|
142
|
+
type: match.type_identifier ?? match.type,
|
|
143
|
+
...(match.authData?.url ? { url: match.authData.url } : {}),
|
|
144
|
+
},
|
|
145
|
+
};
|
|
133
146
|
}
|
|
134
147
|
catch (err) {
|
|
135
148
|
const msg = err?.response?.data?.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
|
package/dist/tools/rca-agent.js
CHANGED
|
@@ -9,6 +9,15 @@ import { handleMCPError } from "../lib/utils.js";
|
|
|
9
9
|
import { trackMCP } from "../index.js";
|
|
10
10
|
import { NEEDS_BUILD_ID, NEEDS_TEST_IDS } from "./tool-handoff.js";
|
|
11
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
|
+
}
|
|
12
21
|
// Tool function to fetch build ID
|
|
13
22
|
export async function getBuildIdTool(args, config) {
|
|
14
23
|
try {
|
|
@@ -26,8 +35,8 @@ export async function getBuildIdTool(args, config) {
|
|
|
26
35
|
};
|
|
27
36
|
}
|
|
28
37
|
catch (error) {
|
|
29
|
-
|
|
30
|
-
|
|
38
|
+
const errorMessage = describeBuildIdError(error, args);
|
|
39
|
+
logger.error("Error fetching build ID: %s", errorMessage);
|
|
31
40
|
return {
|
|
32
41
|
content: [
|
|
33
42
|
{
|
|
@@ -56,8 +65,8 @@ export async function listBuildIdTool(args, config) {
|
|
|
56
65
|
};
|
|
57
66
|
}
|
|
58
67
|
catch (error) {
|
|
59
|
-
|
|
60
|
-
|
|
68
|
+
const errorMessage = describeBuildIdError(error, args);
|
|
69
|
+
logger.error("Error fetching build ID: %s", errorMessage);
|
|
61
70
|
return {
|
|
62
71
|
content: [
|
|
63
72
|
{
|
|
@@ -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");
|
package/dist/tools/selfheal.js
CHANGED
|
@@ -18,9 +18,10 @@ function resolveBrowserStackAuth(config) {
|
|
|
18
18
|
return { config };
|
|
19
19
|
}
|
|
20
20
|
const CREDS_PROMPT_TEXT = "BrowserStack credentials are not configured on the MCP server. " +
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"restart the MCP server, and retry.
|
|
21
|
+
"Set BROWSERSTACK_USERNAME and BROWSERSTACK_ACCESS_KEY in the server " +
|
|
22
|
+
"environment (https://www.browserstack.com/accounts/profile/details), " +
|
|
23
|
+
"restart the MCP server, and retry. Credentials are read from the server " +
|
|
24
|
+
"environment, not from chat.";
|
|
24
25
|
function credsMissingResult() {
|
|
25
26
|
return {
|
|
26
27
|
content: [{ type: "text", text: CREDS_PROMPT_TEXT }],
|
|
@@ -45,20 +46,13 @@ function trimOrUndefined(value) {
|
|
|
45
46
|
const trimmed = value.trim();
|
|
46
47
|
return trimmed.length > 0 ? trimmed : undefined;
|
|
47
48
|
}
|
|
48
|
-
/**
|
|
49
|
-
* Wraps per-status guidance in a HEAD-OF-RESPONSE banner the LLM is unlikely
|
|
50
|
-
* to paraphrase away. The banner must come BEFORE the plan JSON so the model
|
|
51
|
-
* anchors on it when composing its user-facing reply.
|
|
52
|
-
*/
|
|
49
|
+
/** Per-status notes, placed ahead of the plan JSON. */
|
|
53
50
|
function buildWarningBanner(body) {
|
|
54
51
|
return [
|
|
55
|
-
"##
|
|
52
|
+
"## Test code fetch did not return usable source",
|
|
56
53
|
"",
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
"possible — do NOT compress multiple statuses into a generic " +
|
|
60
|
-
"'credentials or session issue' message. If the status below is " +
|
|
61
|
-
"`non_sdk_build`, it is definitely NOT a credentials problem.",
|
|
54
|
+
"Each status below has a different cause and is reported separately; " +
|
|
55
|
+
"a `non_sdk_build` status is not a credentials problem.",
|
|
62
56
|
"",
|
|
63
57
|
body,
|
|
64
58
|
"",
|
|
@@ -236,7 +230,7 @@ export async function fetchSelfHealSelectorTool(args, config) {
|
|
|
236
230
|
"Self-healing report fetched successfully (buildUuid mode). " +
|
|
237
231
|
"Work session-by-session: for each entry in `healing_logs[]`, " +
|
|
238
232
|
"pass `healed_selectors[]` to `prepareSelfHealingPlan` so the " +
|
|
239
|
-
"
|
|
233
|
+
"caller can apply the edits with its own file-editing " +
|
|
240
234
|
"tools (this server never writes files).\n" +
|
|
241
235
|
JSON.stringify(report, null, 2) +
|
|
242
236
|
(testCodeContext
|
|
@@ -263,9 +257,9 @@ export async function fetchSelfHealSelectorTool(args, config) {
|
|
|
263
257
|
const PLAN_INSTRUCTIONS = [
|
|
264
258
|
"## How to use this plan",
|
|
265
259
|
"",
|
|
266
|
-
"This tool does
|
|
267
|
-
"and per-session test source code so
|
|
268
|
-
"
|
|
260
|
+
"This tool does not modify any files. It returns the healed-locator plan",
|
|
261
|
+
"and per-session test source code so the caller can make targeted edits",
|
|
262
|
+
"with its own file-editing tools.",
|
|
269
263
|
"",
|
|
270
264
|
"For each session in the plan:",
|
|
271
265
|
" 1. Read each `tests[].code` to understand the test intent — especially",
|
|
@@ -273,10 +267,10 @@ const PLAN_INSTRUCTIONS = [
|
|
|
273
267
|
" 2. Locate the exact call site(s) in the user's local project that",
|
|
274
268
|
" correspond to the original locator. Use `tests[].filename` as the",
|
|
275
269
|
" first place to look.",
|
|
276
|
-
" 3. Edit
|
|
277
|
-
" A single id / class value may appear in many places
|
|
278
|
-
" find/replace across the repo (e.g. two different elements
|
|
279
|
-
' id="foo" must be resolved individually).',
|
|
270
|
+
" 3. Edit only the call sites that belong to this session's failing step.",
|
|
271
|
+
" A single id / class value may appear in many places, so a blanket",
|
|
272
|
+
" find/replace across the repo is unsafe (e.g. two different elements",
|
|
273
|
+
' both using id="foo" must be resolved individually).',
|
|
280
274
|
" 4. The healing report speaks in CSS/xpath, but the source often uses",
|
|
281
275
|
" `By.id(...)`, `By.name(...)`, `By.css(...)` wrappers. Translate as",
|
|
282
276
|
" needed — e.g. `*[id=\"email-field\"]` → `By.id('email-field')`, or",
|
|
@@ -285,7 +279,7 @@ const PLAN_INSTRUCTIONS = [
|
|
|
285
279
|
"",
|
|
286
280
|
"If `tests[]` is empty for a session, the BrowserStack API did not return",
|
|
287
281
|
"test code (credentials missing, or the session has no associated test",
|
|
288
|
-
"runs).
|
|
282
|
+
"runs). The local file path is needed before editing.",
|
|
289
283
|
].join("\n");
|
|
290
284
|
export async function prepareSelfHealingPlanTool(args, config) {
|
|
291
285
|
const rawArgs = args;
|
|
@@ -389,7 +383,7 @@ export async function prepareSelfHealingPlanTool(args, config) {
|
|
|
389
383
|
}
|
|
390
384
|
catch (error) {
|
|
391
385
|
logger.warn("Failed to fetch test code during plan preparation", error);
|
|
392
|
-
warningBanner = buildWarningBanner(`### Transport error while fetching test code\n\nDiagnosis: ${error instanceof Error ? error.message : String(error)}. This is not an auth issue.\n\
|
|
386
|
+
warningBanner = buildWarningBanner(`### Transport error while fetching test code\n\nDiagnosis: ${error instanceof Error ? error.message : String(error)}. This is not an auth issue.\n\nNext step: retry the fetch, or apply the healed locators to the local test file if the user shares its path.`);
|
|
393
387
|
}
|
|
394
388
|
}
|
|
395
389
|
else if (!resolved && sessionIds.length > 0) {
|
|
@@ -400,14 +394,10 @@ export async function prepareSelfHealingPlanTool(args, config) {
|
|
|
400
394
|
"configured on the MCP server, so test code could not be fetched. " +
|
|
401
395
|
"The plan was still generated from the locator pairs you sent.",
|
|
402
396
|
"",
|
|
403
|
-
"
|
|
404
|
-
"
|
|
405
|
-
"
|
|
406
|
-
"
|
|
407
|
-
"the MCP server, or point me at the local test file directly so " +
|
|
408
|
-
'I can apply the healed locators."',
|
|
409
|
-
"",
|
|
410
|
-
"Do NOT ask the user to paste credentials in chat.",
|
|
397
|
+
"Next step: set BROWSERSTACK_USERNAME and BROWSERSTACK_ACCESS_KEY in " +
|
|
398
|
+
"the server environment and restart the MCP server, or apply the " +
|
|
399
|
+
"healed locators to the local test file if the user shares its path. " +
|
|
400
|
+
"Credentials are read from the server environment, not from chat.",
|
|
411
401
|
].join("\n"));
|
|
412
402
|
}
|
|
413
403
|
const skippedNote = skipped.length > 0
|
|
@@ -3,6 +3,7 @@ import { z } from "zod";
|
|
|
3
3
|
import { formatAxiosError } from "../../lib/error.js";
|
|
4
4
|
import { getBrowserStackAuth } from "../../lib/get-auth.js";
|
|
5
5
|
import { getTMBaseURL } from "../../lib/tm-base-url.js";
|
|
6
|
+
import { wrapUntrusted } from "../../lib/untrusted-content.js";
|
|
6
7
|
/**
|
|
7
8
|
* Schema for fetching a single sub-test-plan by identifier under a parent test
|
|
8
9
|
* plan, including its linked test runs.
|
|
@@ -106,7 +107,9 @@ export async function getSubTestPlan(args, config) {
|
|
|
106
107
|
`Sub-Test-Plan ${plan.identifier}: ${plan.name}`,
|
|
107
108
|
`Parent plan: ${plan.parent_plan_id}`,
|
|
108
109
|
`Status: ${plan.active_state}`,
|
|
109
|
-
plan.description
|
|
110
|
+
plan.description
|
|
111
|
+
? `Description:\n${wrapUntrusted("sub-test-plan description", plan.description)}`
|
|
112
|
+
: null,
|
|
110
113
|
tagsLine,
|
|
111
114
|
issuesLine,
|
|
112
115
|
plan.start_date || plan.end_date
|
|
@@ -133,12 +136,12 @@ export async function getSubTestPlan(args, config) {
|
|
|
133
136
|
{ type: "text", text: header + runsBlock },
|
|
134
137
|
{
|
|
135
138
|
type: "text",
|
|
136
|
-
text: JSON.stringify({
|
|
139
|
+
text: wrapUntrusted("sub-test-plan (structured data)", JSON.stringify({
|
|
137
140
|
sub_test_plan: plan,
|
|
138
141
|
linked_test_runs: runs,
|
|
139
142
|
status_summary: statusSummary,
|
|
140
143
|
total_test_cases: totalCases,
|
|
141
|
-
}, null, 2),
|
|
144
|
+
}, null, 2)),
|
|
142
145
|
},
|
|
143
146
|
],
|
|
144
147
|
};
|
|
@@ -3,6 +3,7 @@ import { z } from "zod";
|
|
|
3
3
|
import { formatAxiosError } from "../../lib/error.js";
|
|
4
4
|
import { getBrowserStackAuth } from "../../lib/get-auth.js";
|
|
5
5
|
import { getTMBaseURL } from "../../lib/tm-base-url.js";
|
|
6
|
+
import { wrapUntrusted } from "../../lib/untrusted-content.js";
|
|
6
7
|
/**
|
|
7
8
|
* Schema for fetching a single test plan by identifier, including its linked test runs.
|
|
8
9
|
*/
|
|
@@ -58,7 +59,9 @@ export async function getTestPlan(args, config) {
|
|
|
58
59
|
const header = [
|
|
59
60
|
`Test Plan ${plan.identifier}: ${plan.name}`,
|
|
60
61
|
`Status: ${plan.active_state}`,
|
|
61
|
-
plan.description
|
|
62
|
+
plan.description
|
|
63
|
+
? `Description:\n${wrapUntrusted("test plan description", plan.description)}`
|
|
64
|
+
: null,
|
|
62
65
|
plan.start_date || plan.end_date
|
|
63
66
|
? `Dates: ${plan.start_date ?? "—"} → ${plan.end_date ?? "—"}`
|
|
64
67
|
: null,
|
|
@@ -83,12 +86,12 @@ export async function getTestPlan(args, config) {
|
|
|
83
86
|
{ type: "text", text: header + runsBlock },
|
|
84
87
|
{
|
|
85
88
|
type: "text",
|
|
86
|
-
text: JSON.stringify({
|
|
89
|
+
text: wrapUntrusted("test plan (structured data)", JSON.stringify({
|
|
87
90
|
test_plan: plan,
|
|
88
91
|
linked_test_runs: runs,
|
|
89
92
|
status_summary: statusSummary,
|
|
90
93
|
total_test_cases: totalCases,
|
|
91
|
-
}, null, 2),
|
|
94
|
+
}, null, 2)),
|
|
92
95
|
},
|
|
93
96
|
],
|
|
94
97
|
};
|
|
@@ -83,7 +83,7 @@ export async function listTestsInFailureThemeTool(args, config) {
|
|
|
83
83
|
}
|
|
84
84
|
export default function addTfaRcaCollaborationTools(server, config) {
|
|
85
85
|
const tools = {};
|
|
86
|
-
tools.tfaRcaTurn = server.tool(TOOL_NAME, "Submit one collaborative RCA turn for a test run to the TFA agent, then poll in-call for a result. Omit threadId on the first turn for a test run — this starts a new investigation thread; every following turn on that same test
|
|
86
|
+
tools.tfaRcaTurn = server.tool(TOOL_NAME, "Submit one collaborative RCA turn for a test run to the TFA agent, then poll in-call for a result. Omit threadId on the first turn for a test run — this starts a new investigation thread; every following turn on that same test passes back the threadId from the previous response, since a test run has only one active thread at a time. Returns status RESOLVED (terminal, includes root_cause/related_prs), NEEDS_INFO (fulfill the asks and submit the next turn with the same threadId), or PENDING (still working — poll it with getTfaTurnResult using the returned turnId; calling tfaRcaTurn again for the same turn submits a duplicate).", TFA_RCA_TURN_PARAMS, async (args, context) => {
|
|
87
87
|
try {
|
|
88
88
|
const result = await tfaRcaTurnTool(args, config, context);
|
|
89
89
|
trackMCP(TOOL_NAME, server.server.getClientVersion(), undefined, config);
|