@browserstack/mcp-server 2.2.2-beta.1 → 2.2.2-beta.2
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 +2 -3
- package/dist/tools/appautomate-utils/appium-sdk/constants.d.ts +1 -1
- package/dist/tools/appautomate-utils/appium-sdk/constants.js +3 -3
- package/dist/tools/appautomate-utils/native-execution/constants.d.ts +1 -1
- package/dist/tools/appautomate-utils/native-execution/constants.js +4 -20
- package/dist/tools/appautomate.js +3 -3
- package/dist/tools/applive.js +4 -4
- package/dist/tools/ask-browserstack/register.js +6 -13
- package/dist/tools/automate-utils/fetch-screenshots.d.ts +2 -5
- package/dist/tools/automate-utils/fetch-screenshots.js +14 -54
- package/dist/tools/automate.js +2 -2
- package/dist/tools/capability-registry/register.js +14 -90
- package/dist/tools/get-failure-logs.js +1 -1
- package/dist/tools/live.js +2 -2
- package/dist/tools/rca-agent-utils/constants.js +2 -2
- package/dist/tools/rca-agent.js +1 -1
- package/dist/tools/sdk-utils/common/constants.d.ts +1 -1
- package/dist/tools/sdk-utils/common/constants.js +1 -1
- package/dist/tools/sdk-utils/common/schema.js +1 -1
- package/dist/tools/selfheal.js +2 -16
- package/dist/tools/testmanagement-utils/create-testcase.js +3 -3
- package/dist/tools/testmanagement-utils/list-testcases.js +1 -1
- package/dist/tools/testmanagement-utils/update-testcase.js +3 -3
- package/dist/tools/testmanagement.js +12 -16
- package/dist/tools/tool-handoff.d.ts +2 -2
- package/dist/tools/tool-handoff.js +10 -16
- package/package.json +1 -1
|
@@ -305,8 +305,7 @@ export default function addAccessibilityTools(server, config) {
|
|
|
305
305
|
return handleMCPError("createAccessibilityAuthConfig", server, config, error);
|
|
306
306
|
}
|
|
307
307
|
});
|
|
308
|
-
tools.getAccessibilityAuthConfig = server.tool("getAccessibilityAuthConfig", "
|
|
309
|
-
NEEDS_A11Y_CONFIG_ID, {
|
|
308
|
+
tools.getAccessibilityAuthConfig = server.tool("getAccessibilityAuthConfig", "Get an accessibility auth config by ID." + NEEDS_A11Y_CONFIG_ID, {
|
|
310
309
|
configId: z.number().describe("ID of the auth configuration to retrieve"),
|
|
311
310
|
}, {
|
|
312
311
|
title: "Get Accessibility Auth Config",
|
|
@@ -317,7 +316,7 @@ export default function addAccessibilityTools(server, config) {
|
|
|
317
316
|
}, async (args) => {
|
|
318
317
|
return await executeGetAuthConfig(args, server, config);
|
|
319
318
|
});
|
|
320
|
-
tools.fetchAccessibilityIssues = server.tool("fetchAccessibilityIssues", "Fetch
|
|
319
|
+
tools.fetchAccessibilityIssues = server.tool("fetchAccessibilityIssues", "Fetch a completed scan's accessibility issues; page with `cursor`." +
|
|
321
320
|
NEEDS_A11Y_SCAN_ID, {
|
|
322
321
|
scanId: z
|
|
323
322
|
.string()
|
|
@@ -21,7 +21,7 @@ export declare const MobileDeviceSchema: z.ZodObject<{
|
|
|
21
21
|
deviceName: z.ZodString;
|
|
22
22
|
osVersion: z.ZodString;
|
|
23
23
|
}, z.core.$strip>;
|
|
24
|
-
export declare const SETUP_APP_AUTOMATE_DESCRIPTION = "Set up
|
|
24
|
+
export declare const SETUP_APP_AUTOMATE_DESCRIPTION = "Set up the App Automate SDK for Appium tests. For pre-built Espresso or XCUITest suites use runAppTestsOnBrowserStack.";
|
|
25
25
|
export declare const SETUP_APP_AUTOMATE_SCHEMA: {
|
|
26
26
|
detectedFramework: z.ZodEnum<typeof AppSDKSupportedFrameworkEnum>;
|
|
27
27
|
detectedTestingFramework: z.ZodEnum<typeof AppSDKSupportedTestingFrameworkEnum>;
|
|
@@ -31,14 +31,14 @@ export const MobileDeviceSchema = z.object({
|
|
|
31
31
|
osVersion: z.string().describe("OS version, e.g. '14', '16', '17', 'latest'"),
|
|
32
32
|
});
|
|
33
33
|
// Tool description and schema for setupBrowserStackAppAutomateTests
|
|
34
|
-
export const SETUP_APP_AUTOMATE_DESCRIPTION = "Set up
|
|
34
|
+
export const SETUP_APP_AUTOMATE_DESCRIPTION = "Set up the App Automate SDK for Appium tests. For pre-built Espresso or XCUITest suites use runAppTestsOnBrowserStack.";
|
|
35
35
|
export const SETUP_APP_AUTOMATE_SCHEMA = {
|
|
36
36
|
detectedFramework: z
|
|
37
37
|
.nativeEnum(AppSDKSupportedFrameworkEnum)
|
|
38
38
|
.describe("The mobile automation framework configured in the project. Example: 'appium'"),
|
|
39
39
|
detectedTestingFramework: z
|
|
40
40
|
.nativeEnum(AppSDKSupportedTestingFrameworkEnum)
|
|
41
|
-
.describe("
|
|
41
|
+
.describe("Test framework in the project, e.g. testng, behave, pytest, robot."),
|
|
42
42
|
detectedLanguage: z
|
|
43
43
|
.nativeEnum(AppSDKSupportedLanguageEnum)
|
|
44
44
|
.describe("The programming language used in the project. Supports Java and C#. Example: 'java', 'csharp'"),
|
|
@@ -46,7 +46,7 @@ export const SETUP_APP_AUTOMATE_SCHEMA = {
|
|
|
46
46
|
.array(MobileDeviceSchema)
|
|
47
47
|
.max(3)
|
|
48
48
|
.default([])
|
|
49
|
-
.describe("
|
|
49
|
+
.describe("Only devices the user asked for, e.g. [['android', 'Samsung Galaxy S24', '14']]."),
|
|
50
50
|
appPath: z
|
|
51
51
|
.string()
|
|
52
52
|
.describe("Path to the mobile app file (.apk for Android, .ipa for iOS). Can be a local file path or a BrowserStack app URL (bs://)"),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { AppTestPlatform } from "./types.js";
|
|
3
|
-
export declare const RUN_APP_AUTOMATE_DESCRIPTION = "
|
|
3
|
+
export declare const RUN_APP_AUTOMATE_DESCRIPTION = "Run pre-built Espresso (Android) or XCUITest (iOS) suites on BrowserStack by upload. Not for Appium: use setupBrowserStackAppAutomateTests.";
|
|
4
4
|
export declare const MobileDeviceSchema: z.ZodObject<{
|
|
5
5
|
platform: z.ZodEnum<{
|
|
6
6
|
android: "android";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { AppTestPlatform } from "./types.js";
|
|
3
|
-
export const RUN_APP_AUTOMATE_DESCRIPTION = `
|
|
3
|
+
export const RUN_APP_AUTOMATE_DESCRIPTION = `Run pre-built Espresso (Android) or XCUITest (iOS) suites on BrowserStack by upload. Not for Appium: use setupBrowserStackAppAutomateTests.`;
|
|
4
4
|
export const MobileDeviceSchema = z.object({
|
|
5
5
|
platform: z
|
|
6
6
|
.enum(["android", "ios"])
|
|
@@ -13,31 +13,15 @@ export const MobileDeviceSchema = z.object({
|
|
|
13
13
|
export const RUN_APP_AUTOMATE_SCHEMA = {
|
|
14
14
|
appPath: z
|
|
15
15
|
.string()
|
|
16
|
-
.describe("
|
|
17
|
-
"If in development IDE directory:\n" +
|
|
18
|
-
"• For Android: 'gradle assembleDebug'\n" +
|
|
19
|
-
"• For iOS:\n" +
|
|
20
|
-
" xcodebuild clean -scheme YOUR_SCHEME && \\\n" +
|
|
21
|
-
" xcodebuild archive -scheme YOUR_SCHEME -configuration Release -archivePath build/app.xcarchive && \\\n" +
|
|
22
|
-
" xcodebuild -exportArchive -archivePath build/app.xcarchive -exportPath build/ipa -exportOptionsPlist exportOptions.plist\n\n" +
|
|
23
|
-
"If in other directory, provide existing app path.\n" +
|
|
24
|
-
"The resolved file must be located inside the directory set in MCP_UPLOAD_BASE_DIR."),
|
|
16
|
+
.describe("Built .apk/.ipa inside MCP_UPLOAD_BASE_DIR (gradle assembleDebug, or xcodebuild archive and export)."),
|
|
25
17
|
testSuitePath: z
|
|
26
18
|
.string()
|
|
27
|
-
.describe("
|
|
28
|
-
"If in development IDE directory:\n" +
|
|
29
|
-
"• For Android: 'gradle assembleAndroidTest'\n" +
|
|
30
|
-
"• For iOS:\n" +
|
|
31
|
-
" xcodebuild test-without-building -scheme YOUR_SCHEME -destination 'generic/platform=iOS' && \\\n" +
|
|
32
|
-
" cd ~/Library/Developer/Xcode/DerivedData/*/Build/Products/Debug-iphonesimulator/ && \\\n" +
|
|
33
|
-
" zip -r Tests.zip *.xctestrun *-Runner.app\n\n" +
|
|
34
|
-
"If in other directory, provide existing test file path.\n" +
|
|
35
|
-
"The resolved file must be located inside the directory set in MCP_UPLOAD_BASE_DIR."),
|
|
19
|
+
.describe("Built suite inside MCP_UPLOAD_BASE_DIR (gradle assembleAndroidTest, or zipped .xctestrun + Runner.app)."),
|
|
36
20
|
devices: z
|
|
37
21
|
.array(MobileDeviceSchema)
|
|
38
22
|
.max(3)
|
|
39
23
|
.default([])
|
|
40
|
-
.describe("
|
|
24
|
+
.describe("Only devices the user asked for, e.g. [['android', 'Samsung Galaxy S24', '14']]."),
|
|
41
25
|
project: z
|
|
42
26
|
.string()
|
|
43
27
|
.optional()
|
|
@@ -194,10 +194,10 @@ async function runAppTestsOnBrowserStack(args, config) {
|
|
|
194
194
|
}
|
|
195
195
|
export default function addAppAutomationTools(server, config) {
|
|
196
196
|
const tools = {};
|
|
197
|
-
tools.takeAppScreenshot = server.tool("takeAppScreenshot", "
|
|
197
|
+
tools.takeAppScreenshot = server.tool("takeAppScreenshot", "Take a screenshot of an app running on a BrowserStack device.", {
|
|
198
198
|
desiredPhone: z
|
|
199
199
|
.string()
|
|
200
|
-
.describe("
|
|
200
|
+
.describe("Exact device name, e.g. 'iPhone 15 Pro Max'. Ask the user."),
|
|
201
201
|
desiredPlatformVersion: z
|
|
202
202
|
.string()
|
|
203
203
|
.describe("The platform version to run the app on. Use 'latest' or 'oldest' for dynamic resolution."),
|
|
@@ -206,7 +206,7 @@ export default function addAppAutomationTools(server, config) {
|
|
|
206
206
|
.describe("Platform to run the app on. Either 'android' or 'ios'."),
|
|
207
207
|
appPath: z
|
|
208
208
|
.string()
|
|
209
|
-
.describe("
|
|
209
|
+
.describe("Local .apk/.ipa path inside MCP_UPLOAD_BASE_DIR."),
|
|
210
210
|
}, {
|
|
211
211
|
title: "Take App Screenshot",
|
|
212
212
|
readOnlyHint: false,
|
package/dist/tools/applive.js
CHANGED
|
@@ -54,19 +54,19 @@ export async function startAppLiveSession(args, config) {
|
|
|
54
54
|
}
|
|
55
55
|
export default function addAppLiveTools(server, config) {
|
|
56
56
|
const tools = {};
|
|
57
|
-
tools.runAppLiveSession = server.tool("runAppLiveSession", "
|
|
57
|
+
tools.runAppLiveSession = server.tool("runAppLiveSession", "Open an app on a real mobile device in App Live for a manual check or crash debugging.", {
|
|
58
58
|
desiredPhone: z
|
|
59
59
|
.string()
|
|
60
|
-
.describe("
|
|
60
|
+
.describe("Exact device name, e.g. 'iPhone 15 Pro Max'. Ask the user; never assume."),
|
|
61
61
|
desiredPlatformVersion: z
|
|
62
62
|
.string()
|
|
63
|
-
.describe("
|
|
63
|
+
.describe("Version number, or 'latest' / 'oldest'."),
|
|
64
64
|
desiredPlatform: z
|
|
65
65
|
.enum(["android", "ios"])
|
|
66
66
|
.describe("Which platform to run on, examples: 'android', 'ios'. Set this based on the app path provided."),
|
|
67
67
|
appPath: z
|
|
68
68
|
.string()
|
|
69
|
-
.describe("
|
|
69
|
+
.describe("Local .apk/.ipa path inside MCP_UPLOAD_BASE_DIR. Ask the user; never assume."),
|
|
70
70
|
}, {
|
|
71
71
|
title: "Run App Live Session",
|
|
72
72
|
readOnlyHint: false,
|
|
@@ -53,19 +53,12 @@ const DESCRIPTION =
|
|
|
53
53
|
// tool that is not enabled for the account cannot do the job at all — so "there is a
|
|
54
54
|
// per-account gate, fall back to the individual tools" is the most useful thing to say
|
|
55
55
|
// first. Parenthesised so it reads as a status note, not as the tool's purpose.
|
|
56
|
-
"(Alpha, limited availability.
|
|
57
|
-
"the
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
"predictable than handing the job to an agent. " +
|
|
63
|
-
"Otherwise, describe what you want in plain language and BrowserStack's agent decides " +
|
|
64
|
-
"which calls to make, then returns its answer plus the steps it took. Anything that would " +
|
|
65
|
-
"change data pauses and asks you to confirm it first; deletes are refused outright. " +
|
|
66
|
-
"Put that confirmation to the user and answer with what THEY say — never decide on " +
|
|
67
|
-
"their behalf. If it cannot be put to anyone, the run is read-only and everything it " +
|
|
68
|
-
"wanted to change comes back in `needs_approval` instead. One task per call.";
|
|
56
|
+
"(Alpha, limited availability. If not enabled it returns an entitlement error; complete " +
|
|
57
|
+
"the task with the individual tools instead.) Use this when no other BrowserStack tool " +
|
|
58
|
+
"here fits, or when the ones you tried did not get you there. Prefer a specific tool " +
|
|
59
|
+
"whenever one fits. Describe the task in plain language. A data change asks you to " +
|
|
60
|
+
"confirm first: put it to the user, never decide for them; deletes are refused " +
|
|
61
|
+
"outright. One task per call.";
|
|
69
62
|
/**
|
|
70
63
|
* `isError` marks a call that FAILED, not one that was refused.
|
|
71
64
|
*
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import { SessionType } from "../../lib/constants.js";
|
|
2
2
|
import { BrowserStackConfig } from "../../lib/types.js";
|
|
3
|
-
export declare function
|
|
4
|
-
export type Screenshot = {
|
|
3
|
+
export declare function fetchAutomationScreenshots(sessionId: string, sessionType: SessionType | undefined, config: BrowserStackConfig): Promise<{
|
|
5
4
|
url: string;
|
|
6
5
|
base64: string;
|
|
7
|
-
|
|
8
|
-
};
|
|
9
|
-
export declare function fetchAutomationScreenshots(sessionId: string, sessionType: SessionType | undefined, config: BrowserStackConfig): Promise<Screenshot[]>;
|
|
6
|
+
}[]>;
|
|
@@ -20,68 +20,28 @@ async function extractScreenshotUrls(sessionId, sessionType, config) {
|
|
|
20
20
|
const text = typeof response.data === "string"
|
|
21
21
|
? response.data
|
|
22
22
|
: JSON.stringify(response.data);
|
|
23
|
-
return parseScreenshotUrls(text);
|
|
24
|
-
}
|
|
25
|
-
const LOG_PREFIX = String.raw `^\d{4}-\d{2}-\d{2} \d{1,2}:\d{1,2}:\d{1,2}:\d{1,3} `;
|
|
26
|
-
const LINE_TYPE_PATTERN = new RegExp(`${LOG_PREFIX}([A-Z_]+) `);
|
|
27
|
-
const SCREENSHOT_REQUEST_PATTERN = new RegExp(String.raw `${LOG_PREFIX}REQUEST \[[^\]]+\] GET /session/[^/\s]+/screenshot(\s|$)`);
|
|
28
|
-
const RESPONSE_VALUE_PATTERN = new RegExp(String.raw `${LOG_PREFIX}RESPONSE \{"value"\s*:\s*"(https://[^"]+)"`);
|
|
29
|
-
const DEBUG_SCREENSHOT_PATTERN = new RegExp(String.raw `${LOG_PREFIX}DEBUG (https://\S+)$`);
|
|
30
|
-
const TRUSTED_HOST_PATTERN = /(^|\.)browserstack\.com$|\.s3([.-][a-z0-9-]+)*\.amazonaws\.com$|\.backblazeb2\.com$/;
|
|
31
|
-
function isTrustedScreenshotUrl(value) {
|
|
32
|
-
try {
|
|
33
|
-
const url = new URL(value);
|
|
34
|
-
return url.protocol === "https:" && TRUSTED_HOST_PATTERN.test(url.hostname);
|
|
35
|
-
}
|
|
36
|
-
catch {
|
|
37
|
-
return false;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
export function parseScreenshotUrls(text) {
|
|
41
23
|
const urls = [];
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
for (let j = i + 1; j < lines.length; j++) {
|
|
54
|
-
const lineType = lines[j].match(LINE_TYPE_PATTERN)?.[1];
|
|
55
|
-
if (lineType === "REQUEST") {
|
|
56
|
-
break;
|
|
57
|
-
}
|
|
58
|
-
if (lineType === "RESPONSE") {
|
|
59
|
-
const valueMatch = lines[j].match(RESPONSE_VALUE_PATTERN);
|
|
60
|
-
if (valueMatch) {
|
|
61
|
-
urls.push(valueMatch[1]);
|
|
62
|
-
}
|
|
63
|
-
break;
|
|
24
|
+
const SCREENSHOT_PATTERN = /REQUEST.*GET.*\/screenshot/;
|
|
25
|
+
const RESPONSE_VALUE_PATTERN = /"value"\s*:\s*"([^"]+)"/;
|
|
26
|
+
// Split logs into lines and process them
|
|
27
|
+
const lines = text.split("\n");
|
|
28
|
+
for (let i = 0; i < lines.length - 1; i++) {
|
|
29
|
+
const currentLine = lines[i];
|
|
30
|
+
const nextLine = lines[i + 1];
|
|
31
|
+
if (SCREENSHOT_PATTERN.test(currentLine)) {
|
|
32
|
+
const match = nextLine.match(RESPONSE_VALUE_PATTERN);
|
|
33
|
+
if (match && match[1]) {
|
|
34
|
+
urls.push(match[1]);
|
|
64
35
|
}
|
|
65
36
|
}
|
|
66
37
|
}
|
|
67
|
-
return
|
|
68
|
-
}
|
|
69
|
-
function detectImageMimeType(buffer) {
|
|
70
|
-
if (buffer[0] === 0xff && buffer[1] === 0xd8 && buffer[2] === 0xff) {
|
|
71
|
-
return "image/jpeg";
|
|
72
|
-
}
|
|
73
|
-
return "image/png";
|
|
38
|
+
return urls;
|
|
74
39
|
}
|
|
75
40
|
async function fetchAndCompress(url) {
|
|
76
41
|
const response = await apiClient.get({ url, responseType: "arraybuffer" });
|
|
77
42
|
// Axios returns response.data as a Buffer for binary data
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
return {
|
|
81
|
-
url,
|
|
82
|
-
base64,
|
|
83
|
-
mimeType: detectImageMimeType(Buffer.from(base64, "base64")),
|
|
84
|
-
};
|
|
43
|
+
const base64 = Buffer.from(response.data).toString("base64");
|
|
44
|
+
return { url, base64: await maybeCompressBase64(base64) };
|
|
85
45
|
}
|
|
86
46
|
//Converts screenshot URLs to base64 encoded images
|
|
87
47
|
async function convertUrlsToBase64(urls) {
|
package/dist/tools/automate.js
CHANGED
|
@@ -14,7 +14,7 @@ export async function fetchAutomationScreenshotsTool(args, config) {
|
|
|
14
14
|
content: [
|
|
15
15
|
{
|
|
16
16
|
type: "text",
|
|
17
|
-
text: "No screenshots found in the session
|
|
17
|
+
text: "No screenshots found in the session or some unexpected error occurred",
|
|
18
18
|
},
|
|
19
19
|
],
|
|
20
20
|
isError: true,
|
|
@@ -23,7 +23,7 @@ export async function fetchAutomationScreenshotsTool(args, config) {
|
|
|
23
23
|
const results = screenshots.map((screenshot, index) => ({
|
|
24
24
|
type: "image",
|
|
25
25
|
data: screenshot.base64,
|
|
26
|
-
mimeType:
|
|
26
|
+
mimeType: "image/png",
|
|
27
27
|
_meta: { url: screenshot.url, index: index + 1 },
|
|
28
28
|
}));
|
|
29
29
|
return {
|
|
@@ -189,24 +189,11 @@ export function addCapabilityRegistryTools(server, deps, config) {
|
|
|
189
189
|
}
|
|
190
190
|
return failed(message, extra);
|
|
191
191
|
};
|
|
192
|
-
tools.listProducts = server.tool("listProducts", "List
|
|
193
|
-
"entities it models, and a line saying what each entity is. START HERE — " +
|
|
194
|
-
"searchCapability needs a product, and this is what tells you which one. If two " +
|
|
195
|
-
"products could both fit the task, ask the user rather than choosing for them. " +
|
|
196
|
-
"ALWAYS pass the user's request, in their own words, as `query`: the answer then " +
|
|
197
|
-
"says outright whether the products clash over the words in it. WHEN IT REPORTS A " +
|
|
198
|
-
"CLASH — two products claiming the same word for different things — ASK THE USER " +
|
|
199
|
-
"WHICH PRODUCT THEY MEAN AND WAIT FOR THEIR ANSWER BEFORE SEARCHING OR INVOKING " +
|
|
200
|
-
"ANYTHING. The response carries the question to put to them and what the shared " +
|
|
201
|
-
"word means in each product. Choosing for them, or searching each product in turn " +
|
|
202
|
-
"and merging the results, is the failure this call exists to prevent.", {
|
|
192
|
+
tools.listProducts = server.tool("listProducts", "List BrowserStack products and their entities. START HERE: searchCapability needs a product name from this. Pass the user's request as `query`; if it reports a clash, ask the user which product they mean and wait. Never pick for them or merge products.", {
|
|
203
193
|
query: z
|
|
204
194
|
.string()
|
|
205
195
|
.optional()
|
|
206
|
-
.describe("The user's request, in their own words
|
|
207
|
-
"`routing` block saying whether the products share the vocabulary this " +
|
|
208
|
-
"request uses — and, when they do, the question to put to the user before " +
|
|
209
|
-
"searching either product."),
|
|
196
|
+
.describe("The user's request, in their own words; enables clash detection."),
|
|
210
197
|
}, {
|
|
211
198
|
title: "List Capability Products",
|
|
212
199
|
readOnlyHint: true,
|
|
@@ -349,9 +336,7 @@ export function addCapabilityRegistryTools(server, deps, config) {
|
|
|
349
336
|
// each. That is the right trade while routing is the problem it solves (you cannot
|
|
350
337
|
// choose between products by looking at one of them), but past a dozen products it may
|
|
351
338
|
// need a names-only default with aliases on request.
|
|
352
|
-
tools.describeEntity = server.tool("describeEntity", "Describe
|
|
353
|
-
"vocabulary the product uses for it. Read this before filtering or writing, because " +
|
|
354
|
-
"ids and field values usually have to be resolved first.", {
|
|
339
|
+
tools.describeEntity = server.tool("describeEntity", "Describe an entity: identifiers, relations and product vocabulary. Read before filtering or writing; ids often need resolving first.", {
|
|
355
340
|
product: productArg().describe(`Which product the entity belongs to: ${productList}.`),
|
|
356
341
|
entity: z.string().describe("Entity name, as listProducts returns it."),
|
|
357
342
|
}, {
|
|
@@ -371,38 +356,10 @@ export function addCapabilityRegistryTools(server, deps, config) {
|
|
|
371
356
|
}
|
|
372
357
|
return ok({ product, entity, ...doc });
|
|
373
358
|
});
|
|
374
|
-
tools.searchCapability = server.tool("searchCapability", "Find
|
|
375
|
-
"CALL listProducts FIRST, passing the user's request as its `query`: it names the " +
|
|
376
|
-
"products, says whether they clash over the words in that request, and gives you " +
|
|
377
|
-
"the question to ask when they do. You need it anyway — `product` here takes a name " +
|
|
378
|
-
"exactly as listProducts spells it. Where two products use the same word for " +
|
|
379
|
-
"different things, ask the USER and wait for an answer rather than picking. " +
|
|
380
|
-
"Search matches the product's OWN words, not synonyms. When your words are not the " +
|
|
381
|
-
"product's, the response says so: `weak_match: true` with a `suggested_vocabulary` " +
|
|
382
|
-
"map of the product's entities and their aliases. Results are still returned, but " +
|
|
383
|
-
"treat them as unconfirmed — pick the closest entity from that map and search again " +
|
|
384
|
-
"using its vocabulary, or call describeEntity on it for the fuller picture. That one " +
|
|
385
|
-
"extra round trip is far cheaper than invoking the wrong capability. " +
|
|
386
|
-
"Narrowing further with `entity` sharpens results. " +
|
|
387
|
-
"THIS IS A SHORTLIST, NOT A CONTRACT. Each result carries only what you need to " +
|
|
388
|
-
"CHOOSE: `name` (the handle), `product`, `mode` (whether it writes), `intent` and " +
|
|
389
|
-
"`guidance` (what it does and what goes wrong), and `method`/`path` for products " +
|
|
390
|
-
"that publish no name yet. " +
|
|
391
|
-
"It does NOT carry parameters or response shapes. Once you have picked one, call " +
|
|
392
|
-
"describeCapability for its full contract, then invokeCapability. Fetching the " +
|
|
393
|
-
"contract only for the one you chose is the difference between ~1k and ~8.6k tokens " +
|
|
394
|
-
"a search. Results are ranked and capped. " +
|
|
395
|
-
"TO SEE EVERYTHING rather than search, pass query='*' — that lists the product " +
|
|
396
|
-
"(honouring `entity` and `mode`) in a stable order instead of ranking it, which is " +
|
|
397
|
-
"what you want when the task is 'what can this product do' rather than a lookup. " +
|
|
398
|
-
"TO PAGE, send `offset`: every response carries `offset` and, when more matched, " +
|
|
399
|
-
"`next_offset` — resend the SAME query with that value. `next_offset` is absent on " +
|
|
400
|
-
"the last page, so walk until it stops rather than comparing counts.", {
|
|
359
|
+
tools.searchCapability = server.tool("searchCapability", "Find capabilities in ONE product by plain language. CALL listProducts FIRST; `product` must be spelled exactly as listProducts spells it. If `weak_match` is true, search again with `suggested_vocabulary` before invoking. Results are a shortlist: call describeCapability for the contract, then invokeCapability. query='*' lists all; page with `next_offset`.", {
|
|
401
360
|
query: z
|
|
402
361
|
.string()
|
|
403
|
-
.describe("
|
|
404
|
-
"instead of search: every capability in the product, in a stable order, " +
|
|
405
|
-
"paginated — use it when you want to see the surface rather than find one thing."),
|
|
362
|
+
.describe("Goal in plain language, or '*' to list every capability."),
|
|
406
363
|
entity: z
|
|
407
364
|
.string()
|
|
408
365
|
.optional()
|
|
@@ -430,9 +387,7 @@ export function addCapabilityRegistryTools(server, deps, config) {
|
|
|
430
387
|
// refused here instead, by a message that names listProducts.
|
|
431
388
|
product: z
|
|
432
389
|
.string()
|
|
433
|
-
.describe("
|
|
434
|
-
"listProducts with the user's request to get the name — and to find out " +
|
|
435
|
-
"whether the products clash over it, in which case ask the user first."),
|
|
390
|
+
.describe("Product name, exactly as listProducts spells it."),
|
|
436
391
|
// `product_choice` USED TO LIVE HERE, AND WAS ITS OWN DEFEAT.
|
|
437
392
|
//
|
|
438
393
|
// A required enum of 'user_confirmed' | 'not_asked' publishes, in the schema every
|
|
@@ -445,18 +400,11 @@ export function addCapabilityRegistryTools(server, deps, config) {
|
|
|
445
400
|
routing_token: z
|
|
446
401
|
.string()
|
|
447
402
|
.optional()
|
|
448
|
-
.describe("
|
|
449
|
-
"refusal — it carries what that step concluded about the user's request, " +
|
|
450
|
-
"signed. Always pass the most recent one. When it reports a clash, send the " +
|
|
451
|
-
"user's answer in `user_words` alongside it; neither works alone."),
|
|
403
|
+
.describe("Latest `routing_token` from listProducts or a clash refusal; send with `user_words` after a clash."),
|
|
452
404
|
user_words: z
|
|
453
405
|
.string()
|
|
454
406
|
.optional()
|
|
455
|
-
.describe("The
|
|
456
|
-
"the product's name or a term only one product uses ('quality gate', 'test " +
|
|
457
|
-
"plan'). Quote them; do not paraphrase, and do not write words the user did " +
|
|
458
|
-
"not say. When a query could mean either product this is what lifts the " +
|
|
459
|
-
"refusal, because it is checked against the index rather than taken on trust."),
|
|
407
|
+
.describe("The user's own words, verbatim, naming the product or a term only it uses. Never paraphrase."),
|
|
460
408
|
mode: z
|
|
461
409
|
.enum(["read", "write", "destructive"])
|
|
462
410
|
.optional()
|
|
@@ -465,8 +413,7 @@ export function addCapabilityRegistryTools(server, deps, config) {
|
|
|
465
413
|
offset: z
|
|
466
414
|
.number()
|
|
467
415
|
.optional()
|
|
468
|
-
.describe("
|
|
469
|
-
"with the SAME query to get the next page. Omit for the first page."),
|
|
416
|
+
.describe("`next_offset` from the previous page, with the same query."),
|
|
470
417
|
}, {
|
|
471
418
|
title: "Search Capabilities",
|
|
472
419
|
readOnlyHint: true,
|
|
@@ -723,16 +670,7 @@ export function addCapabilityRegistryTools(server, deps, config) {
|
|
|
723
670
|
: {}),
|
|
724
671
|
});
|
|
725
672
|
});
|
|
726
|
-
tools.describeCapability = server.tool("describeCapability", "
|
|
727
|
-
"parameters grouped into path_params / query / body under the spec's own names, " +
|
|
728
|
-
"what it returns, and its declared response shapes fully expanded. Call this after " +
|
|
729
|
-
"search and before invokeCapability — search deliberately omits all of it, because " +
|
|
730
|
-
"it is only needed for the one capability you actually call. " +
|
|
731
|
-
"Identify it by `name`, exactly as search returned it; only when a result carries " +
|
|
732
|
-
"no `name` (some products publish none yet) pass `method` and `path` instead. " +
|
|
733
|
-
"Every parameter lists its type, whether it is required, its allowed values where " +
|
|
734
|
-
"the set is closed, and any limits the product declares — obey those before calling " +
|
|
735
|
-
"rather than discovering them from a rejected request.", {
|
|
673
|
+
tools.describeCapability = server.tool("describeCapability", "Full contract (parameters, returns, response shapes) for ONE capability from searchCapability. Call it before invokeCapability. Identify by `name`; use `method` + `path` only when no name was returned.", {
|
|
736
674
|
name: z
|
|
737
675
|
.string()
|
|
738
676
|
.optional()
|
|
@@ -747,14 +685,11 @@ export function addCapabilityRegistryTools(server, deps, config) {
|
|
|
747
685
|
.describe("Path with {placeholders} intact — only for capabilities returned without a `name`."),
|
|
748
686
|
product: productArg()
|
|
749
687
|
.optional()
|
|
750
|
-
.describe(
|
|
751
|
-
"result; required only when two products share a name or a path."),
|
|
688
|
+
.describe("Owning product; needed only when names or paths collide."),
|
|
752
689
|
include_responses: z
|
|
753
690
|
.enum(["success", "all", "none"])
|
|
754
691
|
.optional()
|
|
755
|
-
.describe("
|
|
756
|
-
"(adds the error shapes — several times larger, and near-identical across " +
|
|
757
|
-
"endpoints), or 'none'."),
|
|
692
|
+
.describe("'success' (default), 'all' (adds error shapes, larger) or 'none'."),
|
|
758
693
|
}, {
|
|
759
694
|
title: "Describe Capability",
|
|
760
695
|
readOnlyHint: true,
|
|
@@ -817,17 +752,7 @@ export function addCapabilityRegistryTools(server, deps, config) {
|
|
|
817
752
|
return refuse("describeCapability", "unexpected_error", "that capability could not be described", undefined, identity);
|
|
818
753
|
}
|
|
819
754
|
});
|
|
820
|
-
tools.invokeCapability = server.tool("invokeCapability", "
|
|
821
|
-
"as given — that is the handle. Only when a result carries no `name` (some products " +
|
|
822
|
-
"do not publish them yet) pass `method` and `path` instead, exactly as returned. " +
|
|
823
|
-
"Arguments go in path_params / query / body under the spec's own names. One call " +
|
|
824
|
-
"makes exactly one request and returns the product's own response untouched; when " +
|
|
825
|
-
"`completed` is false there is another page, which you fetch by sending the " +
|
|
826
|
-
"capability's own page parameter. If the mode is 'write' you MUST ask the user " +
|
|
827
|
-
"first, then resend with user_permission='granted' and a change_summary; both are " +
|
|
828
|
-
"recorded. Capabilities whose mode is 'destructive' (deletes) are refused outright — " +
|
|
829
|
-
"archiving, closing and merging are ordinary writes and DO run, so read the mode and " +
|
|
830
|
-
"intent before confirming with the user.", {
|
|
755
|
+
tools.invokeCapability = server.tool("invokeCapability", "Run a capability after describeCapability, by `name` (or `method` + `path` if unnamed); args go in path_params / query / body. If `completed` is false, fetch the next page. Writes: ask the user first, then resend with user_permission='granted' and a change_summary. Deletes are refused; archive, close and merge are writes.", {
|
|
831
756
|
name: z
|
|
832
757
|
.string()
|
|
833
758
|
.optional()
|
|
@@ -855,8 +780,7 @@ export function addCapabilityRegistryTools(server, deps, config) {
|
|
|
855
780
|
.describe("Body fields, under the spec's names."),
|
|
856
781
|
product: productArg()
|
|
857
782
|
.optional()
|
|
858
|
-
.describe(
|
|
859
|
-
"on every result; required only when two products share a name or a path."),
|
|
783
|
+
.describe("Owning product; needed only when names or paths collide."),
|
|
860
784
|
user_permission: z
|
|
861
785
|
.enum(PERMISSION_VALUES)
|
|
862
786
|
.optional()
|
|
@@ -115,7 +115,7 @@ export async function getFailureLogs(args, config) {
|
|
|
115
115
|
// Register tool with the MCP server
|
|
116
116
|
export default function registerGetFailureLogs(server, config) {
|
|
117
117
|
const tools = {};
|
|
118
|
-
tools.getFailureLogs = server.tool("getFailureLogs", "Fetch logs
|
|
118
|
+
tools.getFailureLogs = server.tool("getFailureLogs", "Fetch logs or the video URL for an Automate/App Automate session." +
|
|
119
119
|
NEEDS_SESSION_ID, {
|
|
120
120
|
sessionType: z
|
|
121
121
|
.enum([SessionType.Automate, SessionType.AppAutomate])
|
package/dist/tools/live.js
CHANGED
|
@@ -15,14 +15,14 @@ const LiveArgsShape = {
|
|
|
15
15
|
.describe("Desktop OS ('Windows' or 'OS X') or mobile OS ('android','ios','winphone')"),
|
|
16
16
|
desiredOSVersion: z
|
|
17
17
|
.string()
|
|
18
|
-
.describe("
|
|
18
|
+
.describe("Version number or 'latest'/'oldest'; macOS takes names, e.g. 'Sequoia'."),
|
|
19
19
|
desiredBrowser: z
|
|
20
20
|
.enum(["chrome", "firefox", "safari", "edge", "ie"])
|
|
21
21
|
.describe("Browser for desktop (Chrome, IE, Firefox, Safari, Edge)"),
|
|
22
22
|
desiredBrowserVersion: z
|
|
23
23
|
.string()
|
|
24
24
|
.optional()
|
|
25
|
-
.describe("
|
|
25
|
+
.describe("Desktop browser version, or 'latest' / 'oldest'."),
|
|
26
26
|
desiredDevice: z.string().optional().describe("Device name for mobile"),
|
|
27
27
|
};
|
|
28
28
|
const LiveArgsSchema = z.object(LiveArgsShape);
|
|
@@ -8,7 +8,7 @@ export const FETCH_RCA_PARAMS = {
|
|
|
8
8
|
testId: z
|
|
9
9
|
.array(z.number().int())
|
|
10
10
|
.max(3)
|
|
11
|
-
.describe("
|
|
11
|
+
.describe("Up to 3 numeric test ids from listTestIds (not session ids)."),
|
|
12
12
|
};
|
|
13
13
|
export const GET_BUILD_ID_PARAMS = {
|
|
14
14
|
browserStackProjectName: z
|
|
@@ -21,7 +21,7 @@ export const GET_BUILD_ID_PARAMS = {
|
|
|
21
21
|
export const LIST_TEST_IDS_PARAMS = {
|
|
22
22
|
buildId: z
|
|
23
23
|
.string()
|
|
24
|
-
.describe("
|
|
24
|
+
.describe("Build id; get it with getBuildId or listBuildId."),
|
|
25
25
|
status: z
|
|
26
26
|
.nativeEnum(TestStatus)
|
|
27
27
|
.describe("Filter tests by status. If not provided, all tests are returned. Example for RCA usecase always use failed status"),
|
package/dist/tools/rca-agent.js
CHANGED
|
@@ -131,7 +131,7 @@ export async function listTestIdsTool(args, config) {
|
|
|
131
131
|
}
|
|
132
132
|
export default function addRCATools(server, config) {
|
|
133
133
|
const tools = {};
|
|
134
|
-
tools.fetchRCA = server.tool("fetchRCA", "
|
|
134
|
+
tools.fetchRCA = server.tool("fetchRCA", "AI root cause for failed Automate/App Automate tests. Suggests fixes only; never auto-apply, require explicit user approval." +
|
|
135
135
|
NEEDS_TEST_IDS, FETCH_RCA_PARAMS, {
|
|
136
136
|
title: "Fetch Root Cause Analysis",
|
|
137
137
|
readOnlyHint: true,
|
|
@@ -2,7 +2,7 @@ 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.";
|
|
5
|
-
export declare const RUN_ON_BROWSERSTACK_DESCRIPTION = "Set up and run
|
|
5
|
+
export declare const RUN_ON_BROWSERSTACK_DESCRIPTION = "Set up and run web tests on BrowserStack Automate with the SDK (functional or integration). For Percy visual testing use the Percy tool.";
|
|
6
6
|
export declare const SIMULATE_PERCY_CHANGE_DESCRIPTION = "Integrate Percy visual testing into new projects and demonstrate visual change detection through a step-by-step simulation. This tool handles initial Percy integration, setup, and creates a working demonstration for new users. Primary tool for prompts like: \"Integrate percy for this project\", \"Integrate percy in this project {project_name}\"";
|
|
7
7
|
export declare const PERCY_REPLACE_REGEX: RegExp;
|
|
8
8
|
export declare const PERCY_SNAPSHOT_INSTRUCTION = "\nInvoke listTestFiles() with the provided directories from user to gather all test files in memory ---STEP---\nProcess files in STRICT sequential order using tool addPercySnapshotCommands() with below instructions:\n- Start with index 0\n- Then index 1 \n- Then index 2\n- Continue sequentially until the last index\n- DO NOT skip any index numbers\n- DO NOT jump to random indices like 10, 20, 30\n- DO NOT worry about time consuming and any constraints.\n- DO NOT STOP until you add commands in all the files or you reach end of the files.\n";
|
|
@@ -3,7 +3,7 @@ export const IMPORTANT_SETUP_WARNING = "IMPORTANT: DO NOT SKIP ANY STEP. All the
|
|
|
3
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}";
|
|
4
4
|
export const LIST_TEST_FILES_DESCRIPTION = "Lists all test files for a given set of directories.";
|
|
5
5
|
export const PERCY_SNAPSHOT_COMMANDS_DESCRIPTION = "Adds Percy snapshot commands to the specified test files.";
|
|
6
|
-
export const RUN_ON_BROWSERSTACK_DESCRIPTION = "Set up and run
|
|
6
|
+
export const RUN_ON_BROWSERSTACK_DESCRIPTION = "Set up and run web tests on BrowserStack Automate with the SDK (functional or integration). For Percy visual testing use the Percy tool.";
|
|
7
7
|
export const SIMULATE_PERCY_CHANGE_DESCRIPTION = `Integrate Percy visual testing into new projects and demonstrate visual change detection through a step-by-step simulation. This tool handles initial Percy integration, setup, and creates a working demonstration for new users. Primary tool for prompts like: "Integrate percy for this project", "Integrate percy in this project {project_name}"`;
|
|
8
8
|
export const PERCY_REPLACE_REGEX = /Invoke listTestFiles\(\) with the provided directories[\s\S]*?- DO NOT STOP until you add commands in all the files or you reach end of the files\./;
|
|
9
9
|
export const PERCY_SNAPSHOT_INSTRUCTION = `
|
|
@@ -63,7 +63,7 @@ export const RunTestsOnBrowserStackParamsShape = {
|
|
|
63
63
|
.array(DeviceSchema)
|
|
64
64
|
.max(3)
|
|
65
65
|
.default([])
|
|
66
|
-
.describe("
|
|
66
|
+
.describe("Only devices the user asked for, e.g. [{ platform: 'windows', osVersion: '11', browser: 'chrome', browserVersion: 'latest' }]."),
|
|
67
67
|
};
|
|
68
68
|
export const SetUpPercySchema = z.object(SetUpPercyParamsShape);
|
|
69
69
|
export const RunTestsOnBrowserStackSchema = z.object(RunTestsOnBrowserStackParamsShape);
|
package/dist/tools/selfheal.js
CHANGED
|
@@ -417,11 +417,7 @@ export async function prepareSelfHealingPlanTool(args, config) {
|
|
|
417
417
|
// Registers the fetchSelfHealSelector tool with the MCP server
|
|
418
418
|
export default function addSelfHealTools(server, config) {
|
|
419
419
|
const tools = {};
|
|
420
|
-
tools.fetchSelfHealedSelectors = server.tool("fetchSelfHealedSelectors", "Fetch
|
|
421
|
-
"the run. Provide exactly one of `sessionId` (single Automate / " +
|
|
422
|
-
"App-Automate session) or `buildUuid` (full self-healing report for a " +
|
|
423
|
-
"build). Pass the returned locator pairs to `prepareSelfHealingPlan` " +
|
|
424
|
-
"to plan edits." +
|
|
420
|
+
tools.fetchSelfHealedSelectors = server.tool("fetchSelfHealedSelectors", "Fetch self-healed selectors and the test source. Pass exactly one of `sessionId` or `buildUuid`; give the pairs to prepareSelfHealingPlan." +
|
|
425
421
|
NEEDS_SESSION_ID, {
|
|
426
422
|
sessionId: z
|
|
427
423
|
.string()
|
|
@@ -481,17 +477,7 @@ export default function addSelfHealTools(server, config) {
|
|
|
481
477
|
// Flexible entry point for the `sessions` argument. See normalizers above
|
|
482
478
|
// for the accepted alternate shapes.
|
|
483
479
|
const sessionsFieldSchema = z.preprocess(normalizeSessionsInput, z.array(sessionPayloadSchema).min(1));
|
|
484
|
-
tools.prepareSelfHealingPlan = server.tool("prepareSelfHealingPlan", "Build
|
|
485
|
-
"source code for the calling LLM to apply with its own editing tools. " +
|
|
486
|
-
"This tool does NOT modify files — it returns structured context so " +
|
|
487
|
-
"the LLM can edit only the relevant call sites (avoids blind " +
|
|
488
|
-
"find/replace across shared ids/classes). `sessions` accepts the " +
|
|
489
|
-
"canonical shape `[{sessionId, locators: [{original, healed, " +
|
|
490
|
-
"thought?}]}]`, a single session object, an envelope `{sessions: " +
|
|
491
|
-
"[...]}`, the raw report `{healing_logs: [...]}` (with " +
|
|
492
|
-
"`healed_selectors` aliasing `locators`), and snake_case keys " +
|
|
493
|
-
"(`session_id`, `original_locator`, `healed_locator`, " +
|
|
494
|
-
"`healing_thought`)." +
|
|
480
|
+
tools.prepareSelfHealingPlan = server.tool("prepareSelfHealingPlan", "Build an edit plan from healed locator pairs plus test source; it does NOT modify files. `sessions`: [{sessionId, locators: [{original, healed}]}] or the raw healing report." +
|
|
495
481
|
NEEDS_SESSION_ID, {
|
|
496
482
|
sessions: sessionsFieldSchema.describe("Sessions to plan edits for. See tool description for accepted shapes."),
|
|
497
483
|
}, {
|
|
@@ -15,10 +15,10 @@ export const customFieldValueSchema = z.union([
|
|
|
15
15
|
export const CreateTestCaseSchema = z.object({
|
|
16
16
|
project_identifier: z
|
|
17
17
|
.string()
|
|
18
|
-
.describe("
|
|
18
|
+
.describe("Project id (PR-*). If none, offer createProjectOrFolder."),
|
|
19
19
|
folder_id: z
|
|
20
20
|
.string()
|
|
21
|
-
.describe("
|
|
21
|
+
.describe("Folder id. If none, offer createProjectOrFolder."),
|
|
22
22
|
name: z.string().describe("Name of the test case."),
|
|
23
23
|
description: z
|
|
24
24
|
.string()
|
|
@@ -66,7 +66,7 @@ export const CreateTestCaseSchema = z.object({
|
|
|
66
66
|
priority: z
|
|
67
67
|
.string()
|
|
68
68
|
.optional()
|
|
69
|
-
.describe("Priority
|
|
69
|
+
.describe("Priority, e.g. 'High' or 'medium'; values vary per project; default Medium."),
|
|
70
70
|
case_type: z
|
|
71
71
|
.string()
|
|
72
72
|
.optional()
|
|
@@ -13,7 +13,7 @@ export const ListTestCasesSchema = z.object({
|
|
|
13
13
|
folder_id: z
|
|
14
14
|
.string()
|
|
15
15
|
.optional()
|
|
16
|
-
.describe("
|
|
16
|
+
.describe("Only this folder's cases (ids from listFolders). Omit for all."),
|
|
17
17
|
case_type: z
|
|
18
18
|
.string()
|
|
19
19
|
.optional()
|
|
@@ -37,15 +37,15 @@ export const UpdateTestCaseSchema = z.object({
|
|
|
37
37
|
priority: z
|
|
38
38
|
.string()
|
|
39
39
|
.optional()
|
|
40
|
-
.describe("
|
|
40
|
+
.describe("Priority, e.g. 'High' or 'medium'; values vary per project."),
|
|
41
41
|
case_type: z
|
|
42
42
|
.string()
|
|
43
43
|
.optional()
|
|
44
|
-
.describe("
|
|
44
|
+
.describe("Type, e.g. 'Functional' or 'smoke_sanity'; values vary per project."),
|
|
45
45
|
automation_status: z
|
|
46
46
|
.string()
|
|
47
47
|
.optional()
|
|
48
|
-
.describe("
|
|
48
|
+
.describe("e.g. not_automated, automated, automation_not_required, obsolete."),
|
|
49
49
|
status: z
|
|
50
50
|
.string()
|
|
51
51
|
.optional()
|
|
@@ -441,15 +441,14 @@ export default function addTestManagementTools(server, config) {
|
|
|
441
441
|
destructiveHint: false,
|
|
442
442
|
idempotentHint: false,
|
|
443
443
|
}, (args) => createProjectOrFolderTool(args, config, server));
|
|
444
|
-
tools.createTestCase = server.tool("createTestCase", "
|
|
445
|
-
NEEDS_PROJECT_ID, CreateTestCaseSchema.shape, {
|
|
444
|
+
tools.createTestCase = server.tool("createTestCase", "Create a test case in BrowserStack Test Management." + NEEDS_PROJECT_ID, CreateTestCaseSchema.shape, {
|
|
446
445
|
title: "Create Test Case",
|
|
447
446
|
readOnlyHint: false,
|
|
448
447
|
openWorldHint: false,
|
|
449
448
|
destructiveHint: false,
|
|
450
449
|
idempotentHint: false,
|
|
451
450
|
}, (args) => createTestCaseTool(args, config, server));
|
|
452
|
-
tools.updateTestCase = server.tool("updateTestCase", "Update an existing test case
|
|
451
|
+
tools.updateTestCase = server.tool("updateTestCase", "Update an existing test case; only the supplied fields change." +
|
|
453
452
|
NEEDS_PROJECT_ID, UpdateTestCaseSchema.shape, {
|
|
454
453
|
title: "Update Test Case",
|
|
455
454
|
readOnlyHint: false,
|
|
@@ -457,7 +456,7 @@ export default function addTestManagementTools(server, config) {
|
|
|
457
456
|
destructiveHint: true,
|
|
458
457
|
idempotentHint: true,
|
|
459
458
|
}, (args) => updateTestCaseTool(args, config, server));
|
|
460
|
-
tools.listTestCases = server.tool("listTestCases", "List test cases in a project, optionally
|
|
459
|
+
tools.listTestCases = server.tool("listTestCases", "List test cases in a project, optionally one folder (see listFolders). Filters: case_type, priority, paging." +
|
|
461
460
|
NEEDS_PROJECT_ID, ListTestCasesSchema.shape, {
|
|
462
461
|
title: "List Test Cases",
|
|
463
462
|
readOnlyHint: true,
|
|
@@ -465,7 +464,7 @@ export default function addTestManagementTools(server, config) {
|
|
|
465
464
|
destructiveHint: false,
|
|
466
465
|
idempotentHint: true,
|
|
467
466
|
}, (args) => listTestCasesTool(args, config, server));
|
|
468
|
-
tools.listFolders = server.tool("listFolders", "List folders
|
|
467
|
+
tools.listFolders = server.tool("listFolders", "List folders (id, name, counts) in a project; parent_id lists sub-folders." +
|
|
469
468
|
NEEDS_PROJECT_ID, ListFoldersSchema.shape, {
|
|
470
469
|
title: "List Folders",
|
|
471
470
|
readOnlyHint: true,
|
|
@@ -487,24 +486,21 @@ export default function addTestManagementTools(server, config) {
|
|
|
487
486
|
destructiveHint: false,
|
|
488
487
|
idempotentHint: false,
|
|
489
488
|
}, (args) => createTestRunTool(args, config, server));
|
|
490
|
-
tools.listTestRuns = server.tool("listTestRuns", "List test runs
|
|
491
|
-
NEEDS_PROJECT_ID, ListTestRunsSchema.shape, {
|
|
489
|
+
tools.listTestRuns = server.tool("listTestRuns", "List test runs with filters (dates, assignee, state)." + NEEDS_PROJECT_ID, ListTestRunsSchema.shape, {
|
|
492
490
|
title: "List Test Runs",
|
|
493
491
|
readOnlyHint: true,
|
|
494
492
|
openWorldHint: false,
|
|
495
493
|
destructiveHint: false,
|
|
496
494
|
idempotentHint: true,
|
|
497
495
|
}, (args) => listTestRunsTool(args, config, server));
|
|
498
|
-
tools.updateTestRun = server.tool("updateTestRun", "Update a test run
|
|
499
|
-
NEEDS_PROJECT_ID, UpdateTestRunSchema.shape, {
|
|
496
|
+
tools.updateTestRun = server.tool("updateTestRun", "Update a test run or add test cases to it." + NEEDS_PROJECT_ID, UpdateTestRunSchema.shape, {
|
|
500
497
|
title: "Update Test Run",
|
|
501
498
|
readOnlyHint: false,
|
|
502
499
|
openWorldHint: false,
|
|
503
500
|
destructiveHint: true,
|
|
504
501
|
idempotentHint: true,
|
|
505
502
|
}, (args) => updateTestRunTool(args, config, server));
|
|
506
|
-
tools.addTestResult = server.tool("addTestResult", "Add a test result to a
|
|
507
|
-
NEEDS_PROJECT_ID, AddTestResultSchema.shape, {
|
|
503
|
+
tools.addTestResult = server.tool("addTestResult", "Add a test result to a test run." + NEEDS_PROJECT_ID, AddTestResultSchema.shape, {
|
|
508
504
|
title: "Add Test Result",
|
|
509
505
|
readOnlyHint: false,
|
|
510
506
|
openWorldHint: false,
|
|
@@ -526,7 +522,7 @@ export default function addTestManagementTools(server, config) {
|
|
|
526
522
|
destructiveHint: false,
|
|
527
523
|
idempotentHint: false,
|
|
528
524
|
}, (args, context) => createTestCasesFromFileTool(args, context, config, server));
|
|
529
|
-
tools.createLCASteps = server.tool("createLCASteps", "Generate Low Code Automation (LCA) steps for a test case
|
|
525
|
+
tools.createLCASteps = server.tool("createLCASteps", "Generate Low Code Automation (LCA) steps for a test case." +
|
|
530
526
|
NEEDS_PROJECT_ID, CreateLCAStepsSchema.shape, {
|
|
531
527
|
title: "Create LCA Steps",
|
|
532
528
|
readOnlyHint: false,
|
|
@@ -534,7 +530,7 @@ export default function addTestManagementTools(server, config) {
|
|
|
534
530
|
destructiveHint: false,
|
|
535
531
|
idempotentHint: false,
|
|
536
532
|
}, (args, context) => createLCAStepsTool(args, context, config, server));
|
|
537
|
-
tools.listTestPlans = server.tool("listTestPlans", "List test plans
|
|
533
|
+
tools.listTestPlans = server.tool("listTestPlans", "List test plans (TP-*) with status, dates and run counts; paginated." +
|
|
538
534
|
NEEDS_PROJECT_ID +
|
|
539
535
|
PLAN_WRITES_VIA_CAPABILITIES, ListTestPlansSchema.shape, {
|
|
540
536
|
title: "List Test Plans",
|
|
@@ -543,7 +539,7 @@ export default function addTestManagementTools(server, config) {
|
|
|
543
539
|
destructiveHint: false,
|
|
544
540
|
idempotentHint: true,
|
|
545
541
|
}, (args) => listTestPlansTool(args, config, server));
|
|
546
|
-
tools.getTestPlan = server.tool("getTestPlan", "
|
|
542
|
+
tools.getTestPlan = server.tool("getTestPlan", "Get a test plan (TP-*): metadata, linked runs, case count and status summary." +
|
|
547
543
|
NEEDS_PROJECT_ID +
|
|
548
544
|
NEEDS_TEST_PLAN_ID +
|
|
549
545
|
PLAN_WRITES_VIA_CAPABILITIES, GetTestPlanSchema.shape, {
|
|
@@ -553,7 +549,7 @@ export default function addTestManagementTools(server, config) {
|
|
|
553
549
|
destructiveHint: false,
|
|
554
550
|
idempotentHint: true,
|
|
555
551
|
}, (args) => getTestPlanTool(args, config, server));
|
|
556
|
-
tools.listSubTestPlans = server.tool("listSubTestPlans", "List sub-
|
|
552
|
+
tools.listSubTestPlans = server.tool("listSubTestPlans", "List sub-plans under a test plan (TP-*); paginated." +
|
|
557
553
|
NEEDS_PROJECT_ID +
|
|
558
554
|
NEEDS_TEST_PLAN_ID +
|
|
559
555
|
PLAN_WRITES_VIA_CAPABILITIES, ListSubTestPlansSchema.shape, {
|
|
@@ -563,7 +559,7 @@ export default function addTestManagementTools(server, config) {
|
|
|
563
559
|
destructiveHint: false,
|
|
564
560
|
idempotentHint: true,
|
|
565
561
|
}, (args) => listSubTestPlansTool(args, config, server));
|
|
566
|
-
tools.getSubTestPlan = server.tool("getSubTestPlan", "
|
|
562
|
+
tools.getSubTestPlan = server.tool("getSubTestPlan", "Get a sub-plan (STP-*): metadata and linked runs." +
|
|
567
563
|
NEEDS_PROJECT_ID +
|
|
568
564
|
NEEDS_TEST_PLAN_ID +
|
|
569
565
|
PLAN_WRITES_VIA_CAPABILITIES, GetSubTestPlanSchema.shape, {
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
* twenty hand-written variants drift into twenty different signals.
|
|
24
24
|
*/
|
|
25
25
|
/** No tool lists projects, so this genuinely has to go to the agent. */
|
|
26
|
-
export declare const NEEDS_PROJECT_ID
|
|
26
|
+
export declare const NEEDS_PROJECT_ID = " Needs a project id (PR-*); if you lack one, get it from the capability functions.";
|
|
27
27
|
/** A sibling tool can produce the id — prefer it over the agent. */
|
|
28
28
|
export declare function needsIdFrom(idLabel: string, sourceTool: string): string;
|
|
29
29
|
/**
|
|
@@ -60,7 +60,7 @@ export declare const NEEDS_BUILD_ID: string;
|
|
|
60
60
|
* this branch was open. This used to send the model to askBrowserStackAI for want of a tool;
|
|
61
61
|
* pointing at the agent when a real tool exists is exactly what the header above forbids.
|
|
62
62
|
*/
|
|
63
|
-
export declare const NEEDS_SESSION_ID:
|
|
63
|
+
export declare const NEEDS_SESSION_ID = " Needs a session id: listSessions lists them; getBuildId or listBuildId finds the build.";
|
|
64
64
|
/** A completed scan's ids come from startAccessibilityScan, or from the agent. */
|
|
65
65
|
export declare const NEEDS_A11Y_SCAN_ID: string;
|
|
66
66
|
/** Auth-config ids are not listable by any tool here. */
|
|
@@ -23,9 +23,7 @@
|
|
|
23
23
|
* twenty hand-written variants drift into twenty different signals.
|
|
24
24
|
*/
|
|
25
25
|
/** No tool lists projects, so this genuinely has to go to the agent. */
|
|
26
|
-
export const NEEDS_PROJECT_ID = "
|
|
27
|
-
"have one, use the capability functions to list the Test Management projects, then " +
|
|
28
|
-
"retry this tool with the identifier they return.";
|
|
26
|
+
export const NEEDS_PROJECT_ID = " Needs a project id (PR-*); if you lack one, get it from the capability functions.";
|
|
29
27
|
/** A sibling tool can produce the id — prefer it over the agent. */
|
|
30
28
|
export function needsIdFrom(idLabel, sourceTool) {
|
|
31
29
|
return ` Requires ${idLabel}. Call ${sourceTool} first if you do not have it.`;
|
|
@@ -36,9 +34,8 @@ export function needsIdFrom(idLabel, sourceTool) {
|
|
|
36
34
|
* tool read "Requires a project identifier ... call askBrowserStackAI", which routed
|
|
37
35
|
* "create me a project" through the agent before letting the tool run.
|
|
38
36
|
*/
|
|
39
|
-
export const PROJECT_ID_ONLY_FOR_FOLDER = " Creating a project needs no identifier
|
|
40
|
-
"
|
|
41
|
-
"capability functions to look it up.";
|
|
37
|
+
export const PROJECT_ID_ONLY_FOR_FOLDER = " Creating a project needs no identifier; a folder inside an EXISTING project needs " +
|
|
38
|
+
"its id (PR-*), from the capability functions.";
|
|
42
39
|
/** A test plan id (TP-*) comes from listTestPlans. */
|
|
43
40
|
export const NEEDS_TEST_PLAN_ID = needsIdFrom("a test plan identifier (TP-*)", "listTestPlans");
|
|
44
41
|
/**
|
|
@@ -58,9 +55,8 @@ export const NEEDS_TEST_PLAN_ID = needsIdFrom("a test plan identifier (TP-*)", "
|
|
|
58
55
|
* completes on a client that can show a prompt. On one that cannot, the intended write is
|
|
59
56
|
* refused rather than performed.
|
|
60
57
|
*/
|
|
61
|
-
export const PLAN_WRITES_VIA_CAPABILITIES = " Creating a test plan or sub-plan, and linking or unlinking test runs
|
|
62
|
-
"available as tools here: use the capability functions
|
|
63
|
-
"own API for these operations. They ask you to confirm before changing anything.";
|
|
58
|
+
export const PLAN_WRITES_VIA_CAPABILITIES = " Creating a test plan or sub-plan, and linking or unlinking test runs, are not " +
|
|
59
|
+
"available as tools here: use the capability functions (they ask you to confirm first).";
|
|
64
60
|
/** A build id comes from either build-lookup tool. */
|
|
65
61
|
export const NEEDS_BUILD_ID = needsIdFrom("a BrowserStack build id", "getBuildId or listBuildId");
|
|
66
62
|
/**
|
|
@@ -68,14 +64,12 @@ export const NEEDS_BUILD_ID = needsIdFrom("a BrowserStack build id", "getBuildId
|
|
|
68
64
|
* this branch was open. This used to send the model to askBrowserStackAI for want of a tool;
|
|
69
65
|
* pointing at the agent when a real tool exists is exactly what the header above forbids.
|
|
70
66
|
*/
|
|
71
|
-
export const NEEDS_SESSION_ID = "
|
|
72
|
-
"build either, getBuildId or listBuildId resolves one from a project and build name.";
|
|
67
|
+
export const NEEDS_SESSION_ID = " Needs a session id: listSessions lists them; getBuildId or listBuildId finds the build.";
|
|
73
68
|
/** A completed scan's ids come from startAccessibilityScan, or from the agent. */
|
|
74
|
-
export const NEEDS_A11Y_SCAN_ID = "
|
|
75
|
-
"
|
|
76
|
-
"than guessing.";
|
|
69
|
+
export const NEEDS_A11Y_SCAN_ID = " Needs a completed scan's ids from startAccessibilityScan; past scans are not " +
|
|
70
|
+
"listed, so start a new scan rather than guessing.";
|
|
77
71
|
/** Auth-config ids are not listable by any tool here. */
|
|
78
|
-
export const NEEDS_A11Y_CONFIG_ID = "
|
|
79
|
-
"
|
|
72
|
+
export const NEEDS_A11Y_CONFIG_ID = " Needs the id from createAccessibilityAuthConfig; configs are not listed, so create " +
|
|
73
|
+
"one rather than guessing.";
|
|
80
74
|
/** Test ids come from listTestIds, which itself needs a build id. */
|
|
81
75
|
export const NEEDS_TEST_IDS = needsIdFrom("test ids", "listTestIds");
|