@browserstack/mcp-server 1.2.3-beta.1 → 1.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/README.md +88 -2
  2. package/dist/lib/device-cache.js +20 -17
  3. package/dist/lib/inmemory-store.d.ts +1 -0
  4. package/dist/lib/inmemory-store.js +1 -0
  5. package/dist/lib/utils.d.ts +5 -0
  6. package/dist/lib/utils.js +27 -0
  7. package/dist/server-factory.js +6 -0
  8. package/dist/tools/add-percy-snapshots.d.ts +5 -0
  9. package/dist/tools/add-percy-snapshots.js +17 -0
  10. package/dist/tools/appautomate-utils/appium-sdk/types.d.ts +2 -2
  11. package/dist/tools/appautomate-utils/appium-sdk/types.js +2 -9
  12. package/dist/tools/appautomate-utils/appium-sdk/utils.js +3 -0
  13. package/dist/tools/bstack-sdk.d.ts +2 -15
  14. package/dist/tools/bstack-sdk.js +10 -119
  15. package/dist/tools/build-insights.d.ts +7 -0
  16. package/dist/tools/build-insights.js +67 -0
  17. package/dist/tools/list-test-files.d.ts +2 -0
  18. package/dist/tools/list-test-files.js +36 -0
  19. package/dist/tools/percy-sdk.d.ts +4 -0
  20. package/dist/tools/percy-sdk.js +71 -0
  21. package/dist/tools/percy-snapshot-utils/constants.d.ts +16 -0
  22. package/dist/tools/percy-snapshot-utils/constants.js +500 -0
  23. package/dist/tools/percy-snapshot-utils/detect-test-files.d.ts +10 -0
  24. package/dist/tools/percy-snapshot-utils/detect-test-files.js +175 -0
  25. package/dist/tools/percy-snapshot-utils/types.d.ts +15 -0
  26. package/dist/tools/percy-snapshot-utils/utils.d.ts +4 -0
  27. package/dist/tools/percy-snapshot-utils/utils.js +30 -0
  28. package/dist/tools/rca-agent-utils/constants.d.ts +13 -0
  29. package/dist/tools/rca-agent-utils/constants.js +24 -0
  30. package/dist/tools/rca-agent-utils/format-rca.d.ts +1 -0
  31. package/dist/tools/rca-agent-utils/format-rca.js +37 -0
  32. package/dist/tools/rca-agent-utils/get-build-id.d.ts +1 -0
  33. package/dist/tools/rca-agent-utils/get-build-id.js +18 -0
  34. package/dist/tools/rca-agent-utils/get-failed-test-id.d.ts +2 -0
  35. package/dist/tools/rca-agent-utils/get-failed-test-id.js +69 -0
  36. package/dist/tools/rca-agent-utils/rca-data.d.ts +9 -0
  37. package/dist/tools/rca-agent-utils/rca-data.js +196 -0
  38. package/dist/tools/rca-agent-utils/types.d.ts +48 -0
  39. package/dist/tools/rca-agent-utils/types.js +20 -0
  40. package/dist/tools/rca-agent.d.ts +14 -0
  41. package/dist/tools/rca-agent.js +119 -0
  42. package/dist/tools/review-agent-utils/build-counts.d.ts +7 -0
  43. package/dist/tools/review-agent-utils/build-counts.js +44 -0
  44. package/dist/tools/review-agent-utils/percy-approve-reject.d.ts +6 -0
  45. package/dist/tools/review-agent-utils/percy-approve-reject.js +39 -0
  46. package/dist/tools/review-agent-utils/percy-diffs.d.ts +9 -0
  47. package/dist/tools/review-agent-utils/percy-diffs.js +35 -0
  48. package/dist/tools/review-agent-utils/percy-snapshots.d.ts +11 -0
  49. package/dist/tools/review-agent-utils/percy-snapshots.js +58 -0
  50. package/dist/tools/review-agent.d.ts +5 -0
  51. package/dist/tools/review-agent.js +56 -0
  52. package/dist/tools/run-percy-scan.d.ts +8 -0
  53. package/dist/tools/run-percy-scan.js +37 -0
  54. package/dist/tools/sdk-utils/{commands.d.ts → bstack/commands.d.ts} +1 -1
  55. package/dist/tools/sdk-utils/bstack/commands.js +88 -0
  56. package/dist/tools/sdk-utils/bstack/configUtils.d.ts +4 -0
  57. package/dist/tools/sdk-utils/bstack/configUtils.js +66 -0
  58. package/dist/tools/sdk-utils/bstack/constants.d.ts +58 -0
  59. package/dist/tools/sdk-utils/{constants.js → bstack/constants.js} +117 -78
  60. package/dist/tools/sdk-utils/{constants.d.ts → bstack/frameworks.d.ts} +1 -1
  61. package/dist/tools/sdk-utils/bstack/frameworks.js +57 -0
  62. package/dist/tools/sdk-utils/bstack/index.d.ts +4 -0
  63. package/dist/tools/sdk-utils/bstack/index.js +5 -0
  64. package/dist/tools/sdk-utils/bstack/sdkHandler.d.ts +4 -0
  65. package/dist/tools/sdk-utils/bstack/sdkHandler.js +74 -0
  66. package/dist/tools/sdk-utils/common/constants.d.ts +10 -0
  67. package/dist/tools/sdk-utils/common/constants.js +86 -0
  68. package/dist/tools/sdk-utils/common/formatUtils.d.ts +5 -0
  69. package/dist/tools/sdk-utils/common/formatUtils.js +27 -0
  70. package/dist/tools/sdk-utils/common/index.d.ts +3 -0
  71. package/dist/tools/sdk-utils/common/index.js +4 -0
  72. package/dist/tools/sdk-utils/common/instructionUtils.d.ts +8 -0
  73. package/dist/tools/sdk-utils/common/instructionUtils.js +20 -0
  74. package/dist/tools/sdk-utils/common/schema.d.ts +73 -0
  75. package/dist/tools/sdk-utils/common/schema.js +51 -0
  76. package/dist/tools/sdk-utils/common/types.d.ts +66 -0
  77. package/dist/tools/sdk-utils/{types.js → common/types.js} +15 -2
  78. package/dist/tools/sdk-utils/common/utils.d.ts +25 -0
  79. package/dist/tools/sdk-utils/common/utils.js +90 -0
  80. package/dist/tools/sdk-utils/handler.d.ts +4 -0
  81. package/dist/tools/sdk-utils/handler.js +119 -0
  82. package/dist/tools/sdk-utils/percy-automate/constants.d.ts +11 -0
  83. package/dist/tools/sdk-utils/percy-automate/constants.js +338 -0
  84. package/dist/tools/sdk-utils/percy-automate/frameworks.d.ts +8 -0
  85. package/dist/tools/sdk-utils/percy-automate/frameworks.js +50 -0
  86. package/dist/tools/sdk-utils/percy-automate/handler.d.ts +3 -0
  87. package/dist/tools/sdk-utils/percy-automate/handler.js +30 -0
  88. package/dist/tools/sdk-utils/percy-automate/index.d.ts +1 -0
  89. package/dist/tools/sdk-utils/percy-automate/index.js +2 -0
  90. package/dist/tools/sdk-utils/percy-automate/types.d.ts +13 -0
  91. package/dist/tools/sdk-utils/percy-automate/types.js +1 -0
  92. package/dist/tools/sdk-utils/percy-bstack/constants.d.ts +4 -0
  93. package/dist/tools/sdk-utils/{percy → percy-bstack}/constants.js +13 -39
  94. package/dist/tools/sdk-utils/percy-bstack/frameworks.d.ts +2 -0
  95. package/dist/tools/sdk-utils/percy-bstack/frameworks.js +27 -0
  96. package/dist/tools/sdk-utils/percy-bstack/handler.d.ts +4 -0
  97. package/dist/tools/sdk-utils/percy-bstack/handler.js +99 -0
  98. package/dist/tools/sdk-utils/percy-bstack/index.d.ts +4 -0
  99. package/dist/tools/sdk-utils/percy-bstack/index.js +4 -0
  100. package/dist/tools/sdk-utils/percy-bstack/instructions.d.ts +7 -0
  101. package/dist/tools/sdk-utils/{percy → percy-bstack}/instructions.js +5 -9
  102. package/dist/tools/sdk-utils/percy-bstack/types.d.ts +13 -0
  103. package/dist/tools/sdk-utils/percy-bstack/types.js +5 -0
  104. package/dist/tools/sdk-utils/percy-web/constants.d.ts +41 -0
  105. package/dist/tools/sdk-utils/percy-web/constants.js +883 -0
  106. package/dist/tools/sdk-utils/percy-web/fetchPercyToken.d.ts +4 -0
  107. package/dist/tools/sdk-utils/percy-web/fetchPercyToken.js +32 -0
  108. package/dist/tools/sdk-utils/percy-web/frameworks.d.ts +7 -0
  109. package/dist/tools/sdk-utils/percy-web/frameworks.js +103 -0
  110. package/dist/tools/sdk-utils/percy-web/handler.d.ts +4 -0
  111. package/dist/tools/sdk-utils/percy-web/handler.js +27 -0
  112. package/dist/tools/sdk-utils/percy-web/index.d.ts +4 -0
  113. package/dist/tools/sdk-utils/percy-web/index.js +4 -0
  114. package/dist/tools/sdk-utils/percy-web/types.d.ts +12 -0
  115. package/dist/tools/sdk-utils/percy-web/types.js +1 -0
  116. package/dist/tools/testmanagement-utils/create-testrun.d.ts +4 -4
  117. package/dist/tools/testmanagement-utils/update-testrun.d.ts +4 -4
  118. package/package.json +2 -1
  119. package/dist/tools/sdk-utils/commands.js +0 -65
  120. package/dist/tools/sdk-utils/instructions.d.ts +0 -6
  121. package/dist/tools/sdk-utils/instructions.js +0 -99
  122. package/dist/tools/sdk-utils/percy/constants.d.ts +0 -3
  123. package/dist/tools/sdk-utils/percy/instructions.d.ts +0 -10
  124. package/dist/tools/sdk-utils/percy/types.d.ts +0 -5
  125. package/dist/tools/sdk-utils/types.d.ts +0 -40
  126. /package/dist/tools/{sdk-utils/percy → percy-snapshot-utils}/types.js +0 -0
@@ -0,0 +1,14 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
3
+ import { BrowserStackConfig } from "../lib/types.js";
4
+ import { TestStatus } from "./rca-agent-utils/types.js";
5
+ import { BuildIdArgs } from "./rca-agent-utils/types.js";
6
+ export declare function getBuildIdTool(args: BuildIdArgs, config: BrowserStackConfig): Promise<CallToolResult>;
7
+ export declare function fetchRCADataTool(args: {
8
+ testId: string[];
9
+ }, config: BrowserStackConfig): Promise<CallToolResult>;
10
+ export declare function listTestIdsTool(args: {
11
+ buildId: string;
12
+ status?: TestStatus;
13
+ }, config: BrowserStackConfig): Promise<CallToolResult>;
14
+ export default function addRCATools(server: McpServer, config: BrowserStackConfig): Record<string, any>;
@@ -0,0 +1,119 @@
1
+ import logger from "../logger.js";
2
+ import { getBrowserStackAuth } from "../lib/get-auth.js";
3
+ import { getBuildId } from "./rca-agent-utils/get-build-id.js";
4
+ import { getTestIds } from "./rca-agent-utils/get-failed-test-id.js";
5
+ import { getRCAData } from "./rca-agent-utils/rca-data.js";
6
+ import { formatRCAData } from "./rca-agent-utils/format-rca.js";
7
+ import { handleMCPError } from "../lib/utils.js";
8
+ import { trackMCP } from "../index.js";
9
+ import { FETCH_RCA_PARAMS, GET_BUILD_ID_PARAMS, LIST_TEST_IDS_PARAMS, } from "./rca-agent-utils/constants.js";
10
+ // Tool function to fetch build ID
11
+ export async function getBuildIdTool(args, config) {
12
+ try {
13
+ const { browserStackProjectName, browserStackBuildName } = args;
14
+ const authString = getBrowserStackAuth(config);
15
+ const [username, accessKey] = authString.split(":");
16
+ const buildId = await getBuildId(browserStackProjectName, browserStackBuildName, username, accessKey);
17
+ return {
18
+ content: [
19
+ {
20
+ type: "text",
21
+ text: buildId,
22
+ },
23
+ ],
24
+ };
25
+ }
26
+ catch (error) {
27
+ logger.error("Error fetching build ID", error);
28
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
29
+ return {
30
+ content: [
31
+ {
32
+ type: "text",
33
+ text: `Error fetching build ID: ${errorMessage}`,
34
+ },
35
+ ],
36
+ };
37
+ }
38
+ }
39
+ // Tool function that fetches RCA data
40
+ export async function fetchRCADataTool(args, config) {
41
+ try {
42
+ const authString = getBrowserStackAuth(config);
43
+ // Limit to first 3 test IDs for performance
44
+ const testIds = args.testId;
45
+ const rcaData = await getRCAData(testIds, authString);
46
+ const formattedData = formatRCAData(rcaData);
47
+ return {
48
+ content: [
49
+ {
50
+ type: "text",
51
+ text: formattedData,
52
+ },
53
+ ],
54
+ };
55
+ }
56
+ catch (error) {
57
+ logger.error("Error fetching RCA data", error);
58
+ throw error;
59
+ }
60
+ }
61
+ export async function listTestIdsTool(args, config) {
62
+ try {
63
+ const { buildId, status } = args;
64
+ const authString = getBrowserStackAuth(config);
65
+ // Get test IDs
66
+ const testIds = await getTestIds(buildId, authString, status);
67
+ return {
68
+ content: [
69
+ {
70
+ type: "text",
71
+ text: JSON.stringify(testIds, null, 2),
72
+ },
73
+ ],
74
+ };
75
+ }
76
+ catch (error) {
77
+ logger.error("Error listing test IDs", error);
78
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
79
+ return {
80
+ content: [
81
+ {
82
+ type: "text",
83
+ text: `Error listing test IDs: ${errorMessage}`,
84
+ },
85
+ ],
86
+ };
87
+ }
88
+ }
89
+ export default function addRCATools(server, config) {
90
+ const tools = {};
91
+ tools.fetchRCA = server.tool("fetchRCA", "Retrieves AI-RCA (Root Cause Analysis) data for a BrowserStack Automate and App-Automate session and provides insights into test failures.", FETCH_RCA_PARAMS, async (args) => {
92
+ try {
93
+ trackMCP("fetchRCA", server.server.getClientVersion(), config);
94
+ return await fetchRCADataTool(args, config);
95
+ }
96
+ catch (error) {
97
+ return handleMCPError("fetchRCA", server, config, error);
98
+ }
99
+ });
100
+ tools.getBuildId = server.tool("getBuildId", "Get the BrowserStack build ID for a given project and build name.", GET_BUILD_ID_PARAMS, async (args) => {
101
+ try {
102
+ trackMCP("getBuildId", server.server.getClientVersion(), config);
103
+ return await getBuildIdTool(args, config);
104
+ }
105
+ catch (error) {
106
+ return handleMCPError("getBuildId", server, config, error);
107
+ }
108
+ });
109
+ tools.listTestIds = server.tool("listTestIds", "List test IDs from a BrowserStack Automate build, optionally filtered by status", LIST_TEST_IDS_PARAMS, async (args) => {
110
+ try {
111
+ trackMCP("listTestIds", server.server.getClientVersion(), config);
112
+ return await listTestIdsTool(args, config);
113
+ }
114
+ catch (error) {
115
+ return handleMCPError("listTestIds", server, config, error);
116
+ }
117
+ });
118
+ return tools;
119
+ }
@@ -0,0 +1,7 @@
1
+ export declare function getPercyBuildCount(percyToken: string): Promise<{
2
+ noBuilds: boolean;
3
+ isFirstBuild: boolean;
4
+ lastBuildId: string | undefined;
5
+ orgId: string | undefined;
6
+ browserIds: string[];
7
+ }>;
@@ -0,0 +1,44 @@
1
+ // Utility for fetching the count of Percy builds and orgId.
2
+ export async function getPercyBuildCount(percyToken) {
3
+ const apiUrl = `https://percy.io/api/v1/builds`;
4
+ const response = await fetch(apiUrl, {
5
+ headers: {
6
+ Authorization: `Token token=${percyToken}`,
7
+ "Content-Type": "application/json",
8
+ },
9
+ });
10
+ if (!response.ok) {
11
+ throw new Error(`Failed to fetch Percy builds: ${response.statusText}`);
12
+ }
13
+ const data = await response.json();
14
+ const builds = data.data ?? [];
15
+ const included = data.included ?? [];
16
+ let isFirstBuild = false;
17
+ let lastBuildId;
18
+ let orgId;
19
+ let browserIds = [];
20
+ if (builds.length === 0) {
21
+ return {
22
+ noBuilds: true,
23
+ isFirstBuild: false,
24
+ lastBuildId: undefined,
25
+ orgId,
26
+ browserIds: [],
27
+ };
28
+ }
29
+ else {
30
+ isFirstBuild = builds.length === 1;
31
+ lastBuildId = builds[0].id;
32
+ }
33
+ // Extract browserIds from the latest build if available
34
+ browserIds =
35
+ builds[0]?.relationships?.browsers?.data
36
+ ?.map((b) => b.id)
37
+ ?.filter((id) => typeof id === "string") ?? [];
38
+ // Extract orgId from the `included` projects block
39
+ const project = included.find((item) => item.type === "projects");
40
+ if (project?.relationships?.organization?.data?.id) {
41
+ orgId = project.relationships.organization.data.id;
42
+ }
43
+ return { noBuilds: false, isFirstBuild, lastBuildId, orgId, browserIds };
44
+ }
@@ -0,0 +1,6 @@
1
+ import { BrowserStackConfig } from "../../lib/types.js";
2
+ import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
3
+ export declare function approveOrDeclinePercyBuild(args: {
4
+ buildId: string;
5
+ action: "approve" | "unapprove" | "reject";
6
+ }, config: BrowserStackConfig): Promise<CallToolResult>;
@@ -0,0 +1,39 @@
1
+ import { getBrowserStackAuth } from "../../lib/get-auth.js";
2
+ export async function approveOrDeclinePercyBuild(args, config) {
3
+ const { buildId, action } = args;
4
+ // Get Basic Auth credentials
5
+ const authString = getBrowserStackAuth(config);
6
+ const auth = Buffer.from(authString).toString("base64");
7
+ // Prepare request body
8
+ const body = {
9
+ data: {
10
+ type: "reviews",
11
+ attributes: { action },
12
+ relationships: {
13
+ build: { data: { type: "builds", id: buildId } },
14
+ },
15
+ },
16
+ };
17
+ // Send request to Percy API
18
+ const response = await fetch("https://percy.io/api/v1/reviews", {
19
+ method: "POST",
20
+ headers: {
21
+ "Content-Type": "application/json",
22
+ Authorization: `Basic ${auth}`,
23
+ },
24
+ body: JSON.stringify(body),
25
+ });
26
+ if (!response.ok) {
27
+ const errorText = await response.text();
28
+ throw new Error(`Percy build ${action} failed: ${response.status} ${errorText}`);
29
+ }
30
+ const result = await response.json();
31
+ return {
32
+ content: [
33
+ {
34
+ type: "text",
35
+ text: `Percy build ${buildId} was ${result.data.attributes["review-state"]} by ${result.data.attributes["action-performed-by"].user_name}`,
36
+ },
37
+ ],
38
+ };
39
+ }
@@ -0,0 +1,9 @@
1
+ export interface PercySnapshotDiff {
2
+ id: string;
3
+ name: string | null;
4
+ title: string;
5
+ description: string | null;
6
+ coordinates: any;
7
+ }
8
+ export declare function getPercySnapshotDiff(snapshotId: string, percyToken: string): Promise<PercySnapshotDiff[]>;
9
+ export declare function getPercySnapshotDiffs(snapshotIds: string[], percyToken: string): Promise<PercySnapshotDiff[]>;
@@ -0,0 +1,35 @@
1
+ export async function getPercySnapshotDiff(snapshotId, percyToken) {
2
+ const apiUrl = `https://percy.io/api/v1/snapshots/${snapshotId}`;
3
+ const response = await fetch(apiUrl, {
4
+ headers: {
5
+ Authorization: `Token token=${percyToken}`,
6
+ "Content-Type": "application/json",
7
+ },
8
+ });
9
+ if (!response.ok) {
10
+ throw new Error(`Failed to fetch Percy snapshot ${snapshotId}: ${response.statusText}`);
11
+ }
12
+ const data = await response.json();
13
+ const pageUrl = data.data.attributes?.name || null;
14
+ const changes = [];
15
+ const comparisons = data.included?.filter((item) => item.type === "comparisons") ?? [];
16
+ for (const comparison of comparisons) {
17
+ const appliedRegions = comparison.attributes?.["applied-regions"] ?? [];
18
+ for (const region of appliedRegions) {
19
+ if (region.ignored)
20
+ continue;
21
+ changes.push({
22
+ id: String(region.id),
23
+ name: pageUrl,
24
+ title: region.change_title,
25
+ description: region.change_description ?? null,
26
+ coordinates: region.coordinates ?? null,
27
+ });
28
+ }
29
+ }
30
+ return changes;
31
+ }
32
+ export async function getPercySnapshotDiffs(snapshotIds, percyToken) {
33
+ const allDiffs = await Promise.all(snapshotIds.map((id) => getPercySnapshotDiff(id, percyToken)));
34
+ return allDiffs.flat();
35
+ }
@@ -0,0 +1,11 @@
1
+ import { BrowserStackConfig } from "../../lib/types.js";
2
+ export declare function getChangedPercySnapshotIds(buildId: string, config: BrowserStackConfig, orgId: string | undefined, browserIds: string[]): Promise<string[]>;
3
+ export declare function constructPercyBuildItemsUrl({ buildId, orgId, category, subcategories, browserIds, widths, groupSnapshotsBy, }: {
4
+ buildId: string;
5
+ orgId: string;
6
+ category?: string[];
7
+ subcategories?: string[];
8
+ browserIds?: string[];
9
+ widths?: string[];
10
+ groupSnapshotsBy?: string;
11
+ }): string;
@@ -0,0 +1,58 @@
1
+ import { getBrowserStackAuth } from "../../lib/get-auth.js";
2
+ import { sanitizeUrlParam } from "../../lib/utils.js";
3
+ // Utility for fetching only the IDs of changed Percy snapshots for a given build.
4
+ export async function getChangedPercySnapshotIds(buildId, config, orgId, browserIds) {
5
+ if (!buildId || !orgId) {
6
+ throw new Error("Failed to fetch AI Summary: Missing build ID or organization ID");
7
+ }
8
+ const urlStr = constructPercyBuildItemsUrl({
9
+ buildId,
10
+ orgId,
11
+ category: ["changed"],
12
+ subcategories: ["unreviewed", "approved", "changes_requested"],
13
+ groupSnapshotsBy: "similar_diff",
14
+ browserIds,
15
+ widths: ["375", "1280", "1920"],
16
+ });
17
+ const authString = getBrowserStackAuth(config);
18
+ const auth = Buffer.from(authString).toString("base64");
19
+ const response = await fetch(urlStr, {
20
+ headers: {
21
+ Authorization: `Basic ${auth}`,
22
+ "Content-Type": "application/json",
23
+ },
24
+ });
25
+ if (!response.ok) {
26
+ throw new Error(`Failed to fetch changed Percy snapshots: ${response.status} ${response.statusText}`);
27
+ }
28
+ const responseData = await response.json();
29
+ const buildItems = responseData.data ?? [];
30
+ if (buildItems.length === 0) {
31
+ return [];
32
+ }
33
+ const snapshotIds = buildItems
34
+ .flatMap((item) => item.attributes?.["snapshot-ids"] ?? [])
35
+ .map((id) => String(id));
36
+ return snapshotIds;
37
+ }
38
+ export function constructPercyBuildItemsUrl({ buildId, orgId, category = [], subcategories = [], browserIds = [], widths = [], groupSnapshotsBy, }) {
39
+ const url = new URL("https://percy.io/api/v1/build-items");
40
+ url.searchParams.set("filter[build-id]", sanitizeUrlParam(buildId));
41
+ url.searchParams.set("filter[organization-id]", sanitizeUrlParam(orgId));
42
+ if (category && category.length > 0) {
43
+ category.forEach((cat) => url.searchParams.append("filter[category][]", sanitizeUrlParam(cat)));
44
+ }
45
+ if (subcategories && subcategories.length > 0) {
46
+ subcategories.forEach((sub) => url.searchParams.append("filter[subcategories][]", sanitizeUrlParam(sub)));
47
+ }
48
+ if (browserIds && browserIds.length > 0) {
49
+ browserIds.forEach((id) => url.searchParams.append("filter[browser_ids][]", sanitizeUrlParam(id)));
50
+ }
51
+ if (widths && widths.length > 0) {
52
+ widths.forEach((w) => url.searchParams.append("filter[widths][]", sanitizeUrlParam(w)));
53
+ }
54
+ if (groupSnapshotsBy) {
55
+ url.searchParams.set("filter[group_snapshots_by]", sanitizeUrlParam(groupSnapshotsBy));
56
+ }
57
+ return url.toString();
58
+ }
@@ -0,0 +1,5 @@
1
+ import { BrowserStackConfig } from "../lib/types.js";
2
+ import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
3
+ export declare function fetchPercyChanges(args: {
4
+ project_name: string;
5
+ }, config: BrowserStackConfig): Promise<CallToolResult>;
@@ -0,0 +1,56 @@
1
+ import { getBrowserStackAuth } from "../lib/get-auth.js";
2
+ import { getPercyBuildCount } from "./review-agent-utils/build-counts.js";
3
+ import { getChangedPercySnapshotIds } from "./review-agent-utils/percy-snapshots.js";
4
+ import { PercyIntegrationTypeEnum } from "./sdk-utils/common/types.js";
5
+ import { fetchPercyToken } from "./sdk-utils/percy-web/fetchPercyToken.js";
6
+ import { getPercySnapshotDiffs, } from "./review-agent-utils/percy-diffs.js";
7
+ export async function fetchPercyChanges(args, config) {
8
+ const { project_name } = args;
9
+ const authorization = getBrowserStackAuth(config);
10
+ // Get Percy token for the project
11
+ const percyToken = await fetchPercyToken(project_name, authorization, {
12
+ type: PercyIntegrationTypeEnum.WEB,
13
+ });
14
+ // Get build info (noBuilds, isFirstBuild, lastBuildId)
15
+ const { noBuilds, isFirstBuild, lastBuildId, orgId, browserIds } = await getPercyBuildCount(percyToken);
16
+ if (noBuilds) {
17
+ return {
18
+ content: [
19
+ {
20
+ type: "text",
21
+ text: "No Percy builds found. Please run your first Percy scan to start visual testing.",
22
+ },
23
+ ],
24
+ };
25
+ }
26
+ if (isFirstBuild || !lastBuildId) {
27
+ return {
28
+ content: [
29
+ {
30
+ type: "text",
31
+ text: "This is the first Percy build. No baseline exists to compare changes.",
32
+ },
33
+ ],
34
+ };
35
+ }
36
+ // Get snapshot IDs for the latest build
37
+ const snapshotIds = await getChangedPercySnapshotIds(lastBuildId, config, orgId, browserIds);
38
+ // Fetch all diffs concurrently and flatten results
39
+ const allDiffs = await getPercySnapshotDiffs(snapshotIds, percyToken);
40
+ if (allDiffs.length === 0) {
41
+ return {
42
+ content: [
43
+ {
44
+ type: "text",
45
+ text: "AI Summary is not yet available for this build/framework. There may still be visual changes—please review the build on the dashboard.",
46
+ },
47
+ ],
48
+ };
49
+ }
50
+ return {
51
+ content: allDiffs.map((diff) => ({
52
+ type: "text",
53
+ text: `${diff.name} → ${diff.title}: ${diff.description ?? ""}`,
54
+ })),
55
+ };
56
+ }
@@ -0,0 +1,8 @@
1
+ import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
2
+ import { PercyIntegrationTypeEnum } from "./sdk-utils/common/types.js";
3
+ import { BrowserStackConfig } from "../lib/types.js";
4
+ export declare function runPercyScan(args: {
5
+ projectName: string;
6
+ integrationType: PercyIntegrationTypeEnum;
7
+ instruction?: string;
8
+ }, config: BrowserStackConfig): Promise<CallToolResult>;
@@ -0,0 +1,37 @@
1
+ import { getBrowserStackAuth } from "../lib/get-auth.js";
2
+ import { fetchPercyToken } from "./sdk-utils/percy-web/fetchPercyToken.js";
3
+ export async function runPercyScan(args, config) {
4
+ const { projectName, integrationType, instruction } = args;
5
+ const authorization = getBrowserStackAuth(config);
6
+ const percyToken = await fetchPercyToken(projectName, authorization, {
7
+ type: integrationType,
8
+ });
9
+ const steps = [generatePercyTokenInstructions(percyToken)];
10
+ if (instruction) {
11
+ steps.push(`Use the provided test command with Percy:\n${instruction}`, `If this command fails or is incorrect, fall back to the default approach below.`);
12
+ }
13
+ steps.push(`Attempt to infer the project's test command from context (high confidence commands first):
14
+ - Java → mvn test
15
+ - Python → pytest
16
+ - Node.js → npm test or yarn test
17
+ - Cypress → cypress run
18
+ or from package.json scripts`, `Wrap the inferred command with Percy:\nnpx percy exec -- <test command>`, `If the test command cannot be inferred confidently, ask the user directly for the correct test command.`);
19
+ const instructionContext = steps
20
+ .map((step, index) => `${index + 1}. ${step}`)
21
+ .join("\n\n");
22
+ return {
23
+ content: [
24
+ {
25
+ type: "text",
26
+ text: instructionContext,
27
+ },
28
+ ],
29
+ };
30
+ }
31
+ function generatePercyTokenInstructions(percyToken) {
32
+ return `Set the environment variable for your project:
33
+
34
+ export PERCY_TOKEN="${percyToken}"
35
+
36
+ (For Windows: use 'setx PERCY_TOKEN "${percyToken}"' or 'set PERCY_TOKEN=${percyToken}' as appropriate.)`;
37
+ }
@@ -1,3 +1,3 @@
1
- import { SDKSupportedLanguage } from "./types.js";
1
+ import { SDKSupportedLanguage } from "../common/types.js";
2
2
  export declare function getSDKPrefixCommand(language: SDKSupportedLanguage, framework: string, username: string, accessKey: string): string;
3
3
  export declare function getJavaFrameworkForMaven(framework: string): string;
@@ -0,0 +1,88 @@
1
+ // Constants
2
+ const MAVEN_ARCHETYPE_GROUP_ID = "com.browserstack";
3
+ const MAVEN_ARCHETYPE_ARTIFACT_ID = "browserstack-sdk-archetype-integrate";
4
+ const MAVEN_ARCHETYPE_VERSION = "1.0";
5
+ // Mapping of test frameworks to their corresponding Maven archetype framework names
6
+ const JAVA_FRAMEWORK_MAP = {
7
+ testng: "testng",
8
+ junit5: "junit5",
9
+ junit4: "junit4",
10
+ cucumber: "cucumber-testng",
11
+ };
12
+ // Template for Node.js SDK setup instructions
13
+ const NODEJS_SDK_INSTRUCTIONS = (username, accessKey) => `---STEP---
14
+ Install BrowserStack Node SDK using command:
15
+ \`\`\`bash
16
+ npm i -D browserstack-node-sdk@latest
17
+ \`\`\`
18
+ ---STEP---
19
+ Run the following command to setup browserstack sdk:
20
+ \`\`\`bash
21
+ npx setup --username ${username} --key ${accessKey}
22
+ \`\`\``;
23
+ // Template for Gradle setup instructions (platform-independent)
24
+ const GRADLE_SETUP_INSTRUCTIONS = `
25
+ **For Gradle setup:**
26
+ 1. Add browserstack-java-sdk to dependencies:
27
+ compileOnly 'com.browserstack:browserstack-java-sdk:latest.release'
28
+
29
+ 2. Add browserstackSDK path variable:
30
+ def browserstackSDKArtifact = configurations.compileClasspath.resolvedConfiguration.resolvedArtifacts.find { it.name == 'browserstack-java-sdk' }
31
+
32
+ 3. Add javaagent to gradle tasks:
33
+ jvmArgs "-javaagent:\${browserstackSDKArtifact.file}"
34
+ `;
35
+ // Generates Maven archetype command for Windows platform
36
+ function getMavenCommandForWindows(framework, mavenFramework) {
37
+ return (`mvn archetype:generate -B ` +
38
+ `-DarchetypeGroupId="${MAVEN_ARCHETYPE_GROUP_ID}" ` +
39
+ `-DarchetypeArtifactId="${MAVEN_ARCHETYPE_ARTIFACT_ID}" ` +
40
+ `-DarchetypeVersion="${MAVEN_ARCHETYPE_VERSION}" ` +
41
+ `-DgroupId="${MAVEN_ARCHETYPE_GROUP_ID}" ` +
42
+ `-DartifactId="${MAVEN_ARCHETYPE_ARTIFACT_ID}" ` +
43
+ `-Dversion="${MAVEN_ARCHETYPE_VERSION}" ` +
44
+ `-DBROWSERSTACK_USERNAME="${process.env.BROWSERSTACK_USERNAME}" ` +
45
+ `-DBROWSERSTACK_ACCESS_KEY="${process.env.BROWSERSTACK_ACCESS_KEY}" ` +
46
+ `-DBROWSERSTACK_FRAMEWORK="${mavenFramework}"`);
47
+ }
48
+ // Generates Maven archetype command for Unix-like platforms (macOS/Linux)
49
+ function getMavenCommandForUnix(username, accessKey, mavenFramework) {
50
+ return `mvn archetype:generate -B -DarchetypeGroupId=${MAVEN_ARCHETYPE_GROUP_ID} \\
51
+ -DarchetypeArtifactId=${MAVEN_ARCHETYPE_ARTIFACT_ID} -DarchetypeVersion=${MAVEN_ARCHETYPE_VERSION} \\
52
+ -DgroupId=${MAVEN_ARCHETYPE_GROUP_ID} -DartifactId=${MAVEN_ARCHETYPE_ARTIFACT_ID} -Dversion=${MAVEN_ARCHETYPE_VERSION} \\
53
+ -DBROWSERSTACK_USERNAME="${username}" \\
54
+ -DBROWSERSTACK_ACCESS_KEY="${accessKey}" \\
55
+ -DBROWSERSTACK_FRAMEWORK="${mavenFramework}"`;
56
+ }
57
+ // Generates Java SDK setup instructions with Maven/Gradle options
58
+ function getJavaSDKInstructions(framework, username, accessKey) {
59
+ const mavenFramework = getJavaFrameworkForMaven(framework);
60
+ const isWindows = process.platform === "win32";
61
+ const platformLabel = isWindows ? "Windows" : "macOS/Linux";
62
+ const mavenCommand = isWindows
63
+ ? getMavenCommandForWindows(framework, mavenFramework)
64
+ : getMavenCommandForUnix(username, accessKey, mavenFramework);
65
+ return `---STEP---
66
+ Install BrowserStack Java SDK
67
+
68
+ **Maven command for ${framework} (${platformLabel}):**
69
+ Run the command, it is required to generate the browserstack-sdk-archetype-integrate project:
70
+ ${mavenCommand}
71
+
72
+ Alternative setup for Gradle users:
73
+ ${GRADLE_SETUP_INSTRUCTIONS}`;
74
+ }
75
+ // Main function to get SDK setup commands based on language and framework
76
+ export function getSDKPrefixCommand(language, framework, username, accessKey) {
77
+ switch (language) {
78
+ case "nodejs":
79
+ return NODEJS_SDK_INSTRUCTIONS(username, accessKey);
80
+ case "java":
81
+ return getJavaSDKInstructions(framework, username, accessKey);
82
+ default:
83
+ return "";
84
+ }
85
+ }
86
+ export function getJavaFrameworkForMaven(framework) {
87
+ return JAVA_FRAMEWORK_MAP[framework] || framework;
88
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Utilities for generating BrowserStack configuration files.
3
+ */
4
+ export declare function generateBrowserStackYMLInstructions(desiredPlatforms: string[], enablePercy: boolean | undefined, projectName: string): string;
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Utilities for generating BrowserStack configuration files.
3
+ */
4
+ export function generateBrowserStackYMLInstructions(desiredPlatforms, enablePercy = false, projectName) {
5
+ let ymlContent = `
6
+ # ======================
7
+ # BrowserStack Reporting
8
+ # ======================
9
+ # A single name for your project to organize all your tests. This is required for Percy.
10
+ projectName: ${projectName}
11
+ # TODO: Replace these sample values with your actual project details
12
+ buildName: Sample-Build
13
+
14
+ # =======================================
15
+ # Platforms (Browsers / Devices to test)
16
+ # =======================================
17
+ # Platforms object contains all the browser / device combinations you want to test on.
18
+ # Generate this on the basis of the following platforms requested by the user:
19
+ # Requested platforms: ${desiredPlatforms}
20
+ platforms:
21
+ - os: Windows
22
+ osVersion: 11
23
+ browserName: chrome
24
+ browserVersion: latest
25
+
26
+ # =======================
27
+ # Parallels per Platform
28
+ # =======================
29
+ # The number of parallel threads to be used for each platform set.
30
+ # BrowserStack's SDK runner will select the best strategy based on the configured value
31
+ #
32
+ # Example 1 - If you have configured 3 platforms and set \`parallelsPerPlatform\` as 2, a total of 6 (2 * 3) parallel threads will be used on BrowserStack
33
+ #
34
+ # Example 2 - If you have configured 1 platform and set \`parallelsPerPlatform\` as 5, a total of 5 (1 * 5) parallel threads will be used on BrowserStack
35
+ parallelsPerPlatform: 1
36
+
37
+ # =================
38
+ # Local Testing
39
+ # =================
40
+ # Set to true to test local
41
+ browserstackLocal: true
42
+
43
+ # ===================
44
+ # Debugging features
45
+ # ===================
46
+ debug: true # Visual logs, text logs, etc.
47
+ testObservability: true # For Test Observability`;
48
+ if (enablePercy) {
49
+ ymlContent += `
50
+
51
+ # =====================
52
+ # Percy Visual Testing
53
+ # =====================
54
+ # Set percy to true to enable visual testing.
55
+ # Set percyCaptureMode to 'manual' to control when screenshots are taken.
56
+ percy: true
57
+ percyCaptureMode: manual`;
58
+ }
59
+ return `
60
+ ---STEP---
61
+ Create a browserstack.yml file in the project root. The file should be in the following format:
62
+
63
+ \`\`\`yaml${ymlContent}
64
+ \`\`\`
65
+ \n`;
66
+ }