@browserstack/mcp-server 1.2.15-beta.2 → 1.2.16-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.
Files changed (182) hide show
  1. package/dist/server-factory.js +0 -4
  2. package/dist/tools/percy-sdk.js +20 -11
  3. package/dist/tools/testmanagement-utils/get-testplan.d.ts +16 -0
  4. package/dist/tools/testmanagement-utils/get-testplan.js +99 -0
  5. package/dist/tools/testmanagement-utils/list-folders.d.ts +16 -0
  6. package/dist/tools/testmanagement-utils/list-folders.js +77 -0
  7. package/dist/tools/testmanagement-utils/list-testcases.js +1 -1
  8. package/dist/tools/testmanagement-utils/list-testplans.d.ts +15 -0
  9. package/dist/tools/testmanagement-utils/list-testplans.js +75 -0
  10. package/dist/tools/testmanagement-utils/update-testcase.d.ts +16 -0
  11. package/dist/tools/testmanagement-utils/update-testcase.js +133 -10
  12. package/dist/tools/testmanagement.d.ts +15 -0
  13. package/dist/tools/testmanagement.js +73 -2
  14. package/package.json +2 -3
  15. package/dist/lib/percy-api/auth.d.ts +0 -41
  16. package/dist/lib/percy-api/auth.js +0 -96
  17. package/dist/lib/percy-api/cache.d.ts +0 -28
  18. package/dist/lib/percy-api/cache.js +0 -48
  19. package/dist/lib/percy-api/client.d.ts +0 -69
  20. package/dist/lib/percy-api/client.js +0 -275
  21. package/dist/lib/percy-api/errors.d.ts +0 -15
  22. package/dist/lib/percy-api/errors.js +0 -52
  23. package/dist/lib/percy-api/formatter.d.ts +0 -16
  24. package/dist/lib/percy-api/formatter.js +0 -344
  25. package/dist/lib/percy-api/percy-auth.d.ts +0 -43
  26. package/dist/lib/percy-api/percy-auth.js +0 -137
  27. package/dist/lib/percy-api/percy-error-handler.d.ts +0 -24
  28. package/dist/lib/percy-api/percy-error-handler.js +0 -302
  29. package/dist/lib/percy-api/percy-session.d.ts +0 -42
  30. package/dist/lib/percy-api/percy-session.js +0 -87
  31. package/dist/lib/percy-api/polling.d.ts +0 -26
  32. package/dist/lib/percy-api/polling.js +0 -42
  33. package/dist/lib/percy-api/types.d.ts +0 -56
  34. package/dist/lib/percy-api/types.js +0 -76
  35. package/dist/tools/percy-mcp/advanced/branchline-operations.d.ts +0 -16
  36. package/dist/tools/percy-mcp/advanced/branchline-operations.js +0 -81
  37. package/dist/tools/percy-mcp/advanced/manage-variants.d.ts +0 -16
  38. package/dist/tools/percy-mcp/advanced/manage-variants.js +0 -155
  39. package/dist/tools/percy-mcp/advanced/manage-visual-monitoring.d.ts +0 -16
  40. package/dist/tools/percy-mcp/advanced/manage-visual-monitoring.js +0 -171
  41. package/dist/tools/percy-mcp/auth/auth-status.d.ts +0 -3
  42. package/dist/tools/percy-mcp/auth/auth-status.js +0 -131
  43. package/dist/tools/percy-mcp/core/approve-build.d.ts +0 -14
  44. package/dist/tools/percy-mcp/core/approve-build.js +0 -97
  45. package/dist/tools/percy-mcp/core/get-build-items.d.ts +0 -13
  46. package/dist/tools/percy-mcp/core/get-build-items.js +0 -65
  47. package/dist/tools/percy-mcp/core/get-build.d.ts +0 -10
  48. package/dist/tools/percy-mcp/core/get-build.js +0 -16
  49. package/dist/tools/percy-mcp/core/get-comparison.d.ts +0 -11
  50. package/dist/tools/percy-mcp/core/get-comparison.js +0 -59
  51. package/dist/tools/percy-mcp/core/get-snapshot.d.ts +0 -10
  52. package/dist/tools/percy-mcp/core/get-snapshot.js +0 -40
  53. package/dist/tools/percy-mcp/core/list-builds.d.ts +0 -14
  54. package/dist/tools/percy-mcp/core/list-builds.js +0 -45
  55. package/dist/tools/percy-mcp/core/list-projects.d.ts +0 -12
  56. package/dist/tools/percy-mcp/core/list-projects.js +0 -51
  57. package/dist/tools/percy-mcp/creation/create-app-snapshot.d.ts +0 -12
  58. package/dist/tools/percy-mcp/creation/create-app-snapshot.js +0 -29
  59. package/dist/tools/percy-mcp/creation/create-build.d.ts +0 -19
  60. package/dist/tools/percy-mcp/creation/create-build.js +0 -68
  61. package/dist/tools/percy-mcp/creation/create-comparison.d.ts +0 -18
  62. package/dist/tools/percy-mcp/creation/create-comparison.js +0 -90
  63. package/dist/tools/percy-mcp/creation/create-snapshot.d.ts +0 -17
  64. package/dist/tools/percy-mcp/creation/create-snapshot.js +0 -99
  65. package/dist/tools/percy-mcp/creation/finalize-build.d.ts +0 -12
  66. package/dist/tools/percy-mcp/creation/finalize-build.js +0 -33
  67. package/dist/tools/percy-mcp/creation/finalize-comparison.d.ts +0 -10
  68. package/dist/tools/percy-mcp/creation/finalize-comparison.js +0 -16
  69. package/dist/tools/percy-mcp/creation/finalize-snapshot.d.ts +0 -12
  70. package/dist/tools/percy-mcp/creation/finalize-snapshot.js +0 -33
  71. package/dist/tools/percy-mcp/creation/upload-resource.d.ts +0 -15
  72. package/dist/tools/percy-mcp/creation/upload-resource.js +0 -43
  73. package/dist/tools/percy-mcp/creation/upload-tile.d.ts +0 -11
  74. package/dist/tools/percy-mcp/creation/upload-tile.js +0 -53
  75. package/dist/tools/percy-mcp/diagnostics/analyze-logs-realtime.d.ts +0 -13
  76. package/dist/tools/percy-mcp/diagnostics/analyze-logs-realtime.js +0 -65
  77. package/dist/tools/percy-mcp/diagnostics/get-build-logs.d.ts +0 -17
  78. package/dist/tools/percy-mcp/diagnostics/get-build-logs.js +0 -74
  79. package/dist/tools/percy-mcp/diagnostics/get-network-logs.d.ts +0 -5
  80. package/dist/tools/percy-mcp/diagnostics/get-network-logs.js +0 -21
  81. package/dist/tools/percy-mcp/diagnostics/get-suggestions.d.ts +0 -7
  82. package/dist/tools/percy-mcp/diagnostics/get-suggestions.js +0 -24
  83. package/dist/tools/percy-mcp/index.d.ts +0 -36
  84. package/dist/tools/percy-mcp/index.js +0 -1137
  85. package/dist/tools/percy-mcp/intelligence/get-ai-analysis.d.ts +0 -15
  86. package/dist/tools/percy-mcp/intelligence/get-ai-analysis.js +0 -166
  87. package/dist/tools/percy-mcp/intelligence/get-ai-quota.d.ts +0 -9
  88. package/dist/tools/percy-mcp/intelligence/get-ai-quota.js +0 -73
  89. package/dist/tools/percy-mcp/intelligence/get-build-summary.d.ts +0 -11
  90. package/dist/tools/percy-mcp/intelligence/get-build-summary.js +0 -78
  91. package/dist/tools/percy-mcp/intelligence/get-rca.d.ts +0 -6
  92. package/dist/tools/percy-mcp/intelligence/get-rca.js +0 -153
  93. package/dist/tools/percy-mcp/intelligence/suggest-prompt.d.ts +0 -15
  94. package/dist/tools/percy-mcp/intelligence/suggest-prompt.js +0 -86
  95. package/dist/tools/percy-mcp/intelligence/trigger-ai-recompute.d.ts +0 -16
  96. package/dist/tools/percy-mcp/intelligence/trigger-ai-recompute.js +0 -64
  97. package/dist/tools/percy-mcp/management/create-project.d.ts +0 -14
  98. package/dist/tools/percy-mcp/management/create-project.js +0 -52
  99. package/dist/tools/percy-mcp/management/get-usage-stats.d.ts +0 -12
  100. package/dist/tools/percy-mcp/management/get-usage-stats.js +0 -61
  101. package/dist/tools/percy-mcp/management/manage-browser-targets.d.ts +0 -12
  102. package/dist/tools/percy-mcp/management/manage-browser-targets.js +0 -136
  103. package/dist/tools/percy-mcp/management/manage-comments.d.ts +0 -14
  104. package/dist/tools/percy-mcp/management/manage-comments.js +0 -147
  105. package/dist/tools/percy-mcp/management/manage-ignored-regions.d.ts +0 -18
  106. package/dist/tools/percy-mcp/management/manage-ignored-regions.js +0 -182
  107. package/dist/tools/percy-mcp/management/manage-project-settings.d.ts +0 -16
  108. package/dist/tools/percy-mcp/management/manage-project-settings.js +0 -97
  109. package/dist/tools/percy-mcp/management/manage-tokens.d.ts +0 -14
  110. package/dist/tools/percy-mcp/management/manage-tokens.js +0 -90
  111. package/dist/tools/percy-mcp/management/manage-webhooks.d.ts +0 -15
  112. package/dist/tools/percy-mcp/management/manage-webhooks.js +0 -180
  113. package/dist/tools/percy-mcp/v2/auth-status.d.ts +0 -3
  114. package/dist/tools/percy-mcp/v2/auth-status.js +0 -80
  115. package/dist/tools/percy-mcp/v2/clone-build.d.ts +0 -24
  116. package/dist/tools/percy-mcp/v2/clone-build.js +0 -539
  117. package/dist/tools/percy-mcp/v2/create-app-build.d.ts +0 -28
  118. package/dist/tools/percy-mcp/v2/create-app-build.js +0 -442
  119. package/dist/tools/percy-mcp/v2/create-build.d.ts +0 -16
  120. package/dist/tools/percy-mcp/v2/create-build.js +0 -601
  121. package/dist/tools/percy-mcp/v2/create-project.d.ts +0 -8
  122. package/dist/tools/percy-mcp/v2/create-project.js +0 -33
  123. package/dist/tools/percy-mcp/v2/discover-urls.d.ts +0 -7
  124. package/dist/tools/percy-mcp/v2/discover-urls.js +0 -38
  125. package/dist/tools/percy-mcp/v2/figma-baseline.d.ts +0 -7
  126. package/dist/tools/percy-mcp/v2/figma-baseline.js +0 -18
  127. package/dist/tools/percy-mcp/v2/figma-build.d.ts +0 -7
  128. package/dist/tools/percy-mcp/v2/figma-build.js +0 -39
  129. package/dist/tools/percy-mcp/v2/figma-link.d.ts +0 -6
  130. package/dist/tools/percy-mcp/v2/figma-link.js +0 -27
  131. package/dist/tools/percy-mcp/v2/get-ai-summary.d.ts +0 -5
  132. package/dist/tools/percy-mcp/v2/get-ai-summary.js +0 -109
  133. package/dist/tools/percy-mcp/v2/get-build-detail.d.ts +0 -22
  134. package/dist/tools/percy-mcp/v2/get-build-detail.js +0 -567
  135. package/dist/tools/percy-mcp/v2/get-builds.d.ts +0 -8
  136. package/dist/tools/percy-mcp/v2/get-builds.js +0 -63
  137. package/dist/tools/percy-mcp/v2/get-comparison.d.ts +0 -5
  138. package/dist/tools/percy-mcp/v2/get-comparison.js +0 -94
  139. package/dist/tools/percy-mcp/v2/get-devices.d.ts +0 -5
  140. package/dist/tools/percy-mcp/v2/get-devices.js +0 -33
  141. package/dist/tools/percy-mcp/v2/get-insights.d.ts +0 -7
  142. package/dist/tools/percy-mcp/v2/get-insights.js +0 -52
  143. package/dist/tools/percy-mcp/v2/get-projects.d.ts +0 -6
  144. package/dist/tools/percy-mcp/v2/get-projects.js +0 -41
  145. package/dist/tools/percy-mcp/v2/get-snapshot.d.ts +0 -5
  146. package/dist/tools/percy-mcp/v2/get-snapshot.js +0 -96
  147. package/dist/tools/percy-mcp/v2/get-test-case-history.d.ts +0 -5
  148. package/dist/tools/percy-mcp/v2/get-test-case-history.js +0 -20
  149. package/dist/tools/percy-mcp/v2/get-test-cases.d.ts +0 -6
  150. package/dist/tools/percy-mcp/v2/get-test-cases.js +0 -36
  151. package/dist/tools/percy-mcp/v2/index.d.ts +0 -35
  152. package/dist/tools/percy-mcp/v2/index.js +0 -544
  153. package/dist/tools/percy-mcp/v2/list-integrations.d.ts +0 -5
  154. package/dist/tools/percy-mcp/v2/list-integrations.js +0 -41
  155. package/dist/tools/percy-mcp/v2/manage-domains.d.ts +0 -8
  156. package/dist/tools/percy-mcp/v2/manage-domains.js +0 -33
  157. package/dist/tools/percy-mcp/v2/manage-insights-email.d.ts +0 -8
  158. package/dist/tools/percy-mcp/v2/manage-insights-email.js +0 -49
  159. package/dist/tools/percy-mcp/v2/manage-usage-alerts.d.ts +0 -10
  160. package/dist/tools/percy-mcp/v2/manage-usage-alerts.js +0 -43
  161. package/dist/tools/percy-mcp/v2/migrate-integrations.d.ts +0 -6
  162. package/dist/tools/percy-mcp/v2/migrate-integrations.js +0 -20
  163. package/dist/tools/percy-mcp/v2/preview-comparison.d.ts +0 -5
  164. package/dist/tools/percy-mcp/v2/preview-comparison.js +0 -17
  165. package/dist/tools/percy-mcp/v2/search-build-items.d.ts +0 -12
  166. package/dist/tools/percy-mcp/v2/search-build-items.js +0 -45
  167. package/dist/tools/percy-mcp/workflows/auto-triage.d.ts +0 -7
  168. package/dist/tools/percy-mcp/workflows/auto-triage.js +0 -82
  169. package/dist/tools/percy-mcp/workflows/clone-build.d.ts +0 -22
  170. package/dist/tools/percy-mcp/workflows/clone-build.js +0 -414
  171. package/dist/tools/percy-mcp/workflows/create-percy-build.d.ts +0 -32
  172. package/dist/tools/percy-mcp/workflows/create-percy-build.js +0 -434
  173. package/dist/tools/percy-mcp/workflows/debug-failed-build.d.ts +0 -5
  174. package/dist/tools/percy-mcp/workflows/debug-failed-build.js +0 -122
  175. package/dist/tools/percy-mcp/workflows/diff-explain.d.ts +0 -6
  176. package/dist/tools/percy-mcp/workflows/diff-explain.js +0 -147
  177. package/dist/tools/percy-mcp/workflows/pr-visual-report.d.ts +0 -8
  178. package/dist/tools/percy-mcp/workflows/pr-visual-report.js +0 -184
  179. package/dist/tools/percy-mcp/workflows/run-tests.d.ts +0 -17
  180. package/dist/tools/percy-mcp/workflows/run-tests.js +0 -107
  181. package/dist/tools/percy-mcp/workflows/snapshot-urls.d.ts +0 -18
  182. package/dist/tools/percy-mcp/workflows/snapshot-urls.js +0 -197
@@ -1,147 +0,0 @@
1
- import { PercyClient } from "../../../lib/percy-api/client.js";
2
- import { pollUntil } from "../../../lib/percy-api/polling.js";
3
- export async function percyDiffExplain(args, config) {
4
- const client = new PercyClient(config);
5
- const depth = args.depth || "detailed"; // summary, detailed, full_rca
6
- // Get comparison with AI data
7
- const comparison = await client.get(`/comparisons/${args.comparison_id}`, {}, [
8
- "head-screenshot.image",
9
- "base-screenshot.image",
10
- "diff-image",
11
- "ai-diff-image",
12
- "browser.browser-family",
13
- "comparison-tag",
14
- ]);
15
- if (!comparison) {
16
- return {
17
- content: [{ type: "text", text: "Comparison not found." }],
18
- isError: true,
19
- };
20
- }
21
- let output = `## Visual Diff Explanation — Comparison #${args.comparison_id}\n\n`;
22
- // Basic diff info
23
- const diffRatio = comparison.diffRatio ?? 0;
24
- const aiDiffRatio = comparison.aiDiffRatio;
25
- output += `**Diff:** ${(diffRatio * 100).toFixed(1)}%`;
26
- if (aiDiffRatio !== null && aiDiffRatio !== undefined) {
27
- output += ` | **AI Diff:** ${(aiDiffRatio * 100).toFixed(1)}%`;
28
- const reduction = diffRatio > 0 ? ((1 - aiDiffRatio / diffRatio) * 100).toFixed(0) : "0";
29
- output += ` (${reduction}% noise filtered)`;
30
- }
31
- output += "\n\n";
32
- // Summary depth: AI descriptions only
33
- const regions = comparison.appliedRegions || [];
34
- if (regions.length > 0) {
35
- output += `### What Changed (${regions.length} regions)\n\n`;
36
- regions.forEach((region, i) => {
37
- const type = region.change_type || region.changeType || "unknown";
38
- const title = region.change_title || region.changeTitle || "Untitled change";
39
- const desc = region.change_description || region.changeDescription || "";
40
- const reason = region.change_reason || region.changeReason || "";
41
- const ignored = region.ignored;
42
- output += `${i + 1}. ${ignored ? "~~" : "**"}${title}${ignored ? "~~" : "**"} (${type})`;
43
- if (ignored)
44
- output += " — *ignored by AI*";
45
- output += "\n";
46
- if (desc && depth !== "summary")
47
- output += ` ${desc}\n`;
48
- if (reason && depth !== "summary")
49
- output += ` *Reason: ${reason}*\n`;
50
- output += "\n";
51
- });
52
- }
53
- else if (diffRatio > 0) {
54
- output +=
55
- "No AI region data available. Visual diff detected but not yet analyzed by AI.\n\n";
56
- }
57
- else {
58
- output += "No visual differences detected.\n\n";
59
- }
60
- // Detailed depth: + coordinates
61
- if (depth === "detailed" || depth === "full_rca") {
62
- const coords = comparison.diffRects || comparison.aiDiffRects || [];
63
- if (coords.length > 0) {
64
- output += `### Diff Regions (coordinates)\n\n`;
65
- coords.forEach((rect, i) => {
66
- output += `${i + 1}. (${rect.x || rect.left || 0}, ${rect.y || rect.top || 0}) → (${rect.right || rect.x2 || 0}, ${rect.bottom || rect.y2 || 0})\n`;
67
- });
68
- output += "\n";
69
- }
70
- }
71
- // Full RCA depth: + DOM/CSS changes
72
- if (depth === "full_rca") {
73
- output += `### Root Cause Analysis\n\n`;
74
- try {
75
- // Check if RCA exists, trigger if needed
76
- let rcaData;
77
- try {
78
- rcaData = await client.get("/rca", {
79
- comparison_id: args.comparison_id,
80
- });
81
- }
82
- catch (e) {
83
- if (e.statusCode === 404) {
84
- // Trigger RCA
85
- await client.post("/rca", {
86
- data: {
87
- type: "rca",
88
- attributes: { "comparison-id": args.comparison_id },
89
- },
90
- });
91
- // Poll for result (max 30s for inline use)
92
- rcaData = await pollUntil(async () => {
93
- const data = await client.get("/rca", {
94
- comparison_id: args.comparison_id,
95
- });
96
- if (data?.status === "finished" || data?.status === "failed")
97
- return { done: true, result: data };
98
- return { done: false };
99
- }, { maxTimeoutMs: 30000 });
100
- }
101
- else {
102
- throw e;
103
- }
104
- }
105
- if (rcaData?.status === "finished" && rcaData?.diffNodes) {
106
- const nodes = rcaData.diffNodes;
107
- const commonDiffs = nodes.common_diffs || [];
108
- if (commonDiffs.length > 0) {
109
- commonDiffs.slice(0, 10).forEach((diff, i) => {
110
- const base = diff.base || {};
111
- const head = diff.head || {};
112
- const tag = head.tagName || base.tagName || "element";
113
- const xpath = head.xpath || base.xpath || "";
114
- output += `${i + 1}. **${tag}**`;
115
- if (xpath)
116
- output += ` — \`${xpath}\``;
117
- output += "\n";
118
- const baseAttrs = base.attributes || {};
119
- const headAttrs = head.attributes || {};
120
- for (const key of Object.keys(headAttrs)) {
121
- if (JSON.stringify(baseAttrs[key]) !==
122
- JSON.stringify(headAttrs[key])) {
123
- output += ` ${key}: \`${baseAttrs[key] ?? "none"}\` → \`${headAttrs[key]}\`\n`;
124
- }
125
- }
126
- output += "\n";
127
- });
128
- }
129
- else {
130
- output += "No DOM-level differences identified by RCA.\n";
131
- }
132
- }
133
- else if (rcaData?.status === "failed") {
134
- output +=
135
- "RCA analysis failed — comparison may not have DOM metadata.\n";
136
- }
137
- else {
138
- output +=
139
- "RCA analysis is still processing. Re-run with depth=full_rca later.\n";
140
- }
141
- }
142
- catch (e) {
143
- output += `RCA unavailable: ${e.message}. Falling back to AI-only analysis.\n`;
144
- }
145
- }
146
- return { content: [{ type: "text", text: output }] };
147
- }
@@ -1,8 +0,0 @@
1
- import { BrowserStackConfig } from "../../../lib/types.js";
2
- import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
3
- export declare function percyPrVisualReport(args: {
4
- project_id?: string;
5
- branch?: string;
6
- sha?: string;
7
- build_id?: string;
8
- }, config: BrowserStackConfig): Promise<CallToolResult>;
@@ -1,184 +0,0 @@
1
- import { PercyClient } from "../../../lib/percy-api/client.js";
2
- import { percyCache } from "../../../lib/percy-api/cache.js";
3
- import { formatBuild } from "../../../lib/percy-api/formatter.js";
4
- export async function percyPrVisualReport(args, config) {
5
- const client = new PercyClient(config);
6
- const errors = [];
7
- // Step 1: Resolve build
8
- let build;
9
- try {
10
- if (args.build_id) {
11
- build = await client.get(`/builds/${args.build_id}`, { "include-metadata": "true" }, ["build-summary", "browsers"]);
12
- }
13
- else {
14
- // Find build by branch or SHA
15
- const params = {};
16
- if (args.project_id) {
17
- // Use project-scoped endpoint
18
- }
19
- if (args.branch)
20
- params["filter[branch]"] = args.branch;
21
- if (args.sha)
22
- params["filter[sha]"] = args.sha;
23
- params["page[limit]"] = "1";
24
- const builds = await client.get("/builds", params);
25
- const buildList = Array.isArray(builds)
26
- ? builds
27
- : builds?.data
28
- ? Array.isArray(builds.data)
29
- ? builds.data
30
- : [builds.data]
31
- : [];
32
- if (buildList.length === 0) {
33
- const identifier = args.branch
34
- ? `branch '${args.branch}'`
35
- : args.sha
36
- ? `SHA '${args.sha}'`
37
- : "the given filters";
38
- return {
39
- content: [
40
- {
41
- type: "text",
42
- text: `No Percy build found for ${identifier}. Ensure a Percy build has been created for this branch/commit.`,
43
- },
44
- ],
45
- };
46
- }
47
- const buildId = buildList[0]?.id || buildList[0];
48
- build = await client.get(`/builds/${typeof buildId === "object" ? buildId.id : buildId}`, { "include-metadata": "true" }, ["build-summary", "browsers"]);
49
- }
50
- }
51
- catch (e) {
52
- return {
53
- content: [{ type: "text", text: `Failed to fetch build: ${e.message}` }],
54
- isError: true,
55
- };
56
- }
57
- if (!build) {
58
- return {
59
- content: [{ type: "text", text: "Build not found." }],
60
- isError: true,
61
- };
62
- }
63
- // Cache build data for other composite tools
64
- percyCache.set(`build:${build.id}`, build);
65
- // Step 2: Build header with state awareness
66
- let output = "";
67
- const state = build.state || "unknown";
68
- output += `# Percy Visual Regression Report\n\n`;
69
- output += formatBuild(build);
70
- // Step 3: Get build summary if available
71
- const buildSummary = build.buildSummary;
72
- if (buildSummary?.summary) {
73
- try {
74
- const summaryData = typeof buildSummary.summary === "string"
75
- ? JSON.parse(buildSummary.summary)
76
- : buildSummary.summary;
77
- if (summaryData?.title || summaryData?.items) {
78
- output += `\n### AI Build Summary\n\n`;
79
- if (summaryData.title)
80
- output += `> ${summaryData.title}\n\n`;
81
- if (Array.isArray(summaryData.items)) {
82
- summaryData.items.forEach((item) => {
83
- output += `- ${item.title || item}\n`;
84
- });
85
- output += "\n";
86
- }
87
- }
88
- }
89
- catch {
90
- // Summary parse failed, skip
91
- }
92
- }
93
- // Step 4: Get changed build items
94
- if (state === "finished" || state === "processing") {
95
- let items = [];
96
- try {
97
- const itemsData = await client.get("/build-items", {
98
- "filter[build-id]": build.id,
99
- "filter[category]": "changed",
100
- "page[limit]": "30",
101
- });
102
- items = Array.isArray(itemsData) ? itemsData : [];
103
- }
104
- catch (e) {
105
- errors.push(`[Failed to load changed snapshots: ${e.message}]`);
106
- }
107
- if (items.length === 0 && errors.length === 0) {
108
- output += `\n### No Visual Changes Detected\n\nAll snapshots match the baseline.\n`;
109
- }
110
- else if (items.length > 0) {
111
- // Step 5: Rank by risk
112
- // Critical: AI bug flags > Review: high diff > Expected: content changes > Noise: low diff
113
- const critical = [];
114
- const review = [];
115
- const expected = [];
116
- const noise = [];
117
- for (const item of items) {
118
- const name = item.name || item.snapshotName || "Unknown";
119
- const diffRatio = item.diffRatio ?? item.maxDiffRatio ?? 0;
120
- const potentialBugs = item.totalPotentialBugs || item.aiDetails?.totalPotentialBugs || 0;
121
- const entry = { name, diffRatio, potentialBugs, item };
122
- if (potentialBugs > 0) {
123
- critical.push(entry);
124
- }
125
- else if (diffRatio > 0.15) {
126
- review.push(entry);
127
- }
128
- else if (diffRatio > 0.005) {
129
- expected.push(entry);
130
- }
131
- else {
132
- noise.push(entry);
133
- }
134
- }
135
- output += `\n### Changed Snapshots (${items.length})\n\n`;
136
- if (critical.length > 0) {
137
- output += `**CRITICAL — Potential Bugs (${critical.length}):**\n`;
138
- critical.forEach((e, i) => {
139
- output += `${i + 1}. **${e.name}** — ${(e.diffRatio * 100).toFixed(1)}% diff, ${e.potentialBugs} bug(s) flagged\n`;
140
- });
141
- output += "\n";
142
- }
143
- if (review.length > 0) {
144
- output += `**REVIEW REQUIRED (${review.length}):**\n`;
145
- review.forEach((e, i) => {
146
- output += `${i + 1}. **${e.name}** — ${(e.diffRatio * 100).toFixed(1)}% diff\n`;
147
- });
148
- output += "\n";
149
- }
150
- if (expected.length > 0) {
151
- output += `**EXPECTED CHANGES (${expected.length}):**\n`;
152
- expected.forEach((e, i) => {
153
- output += `${i + 1}. ${e.name} — ${(e.diffRatio * 100).toFixed(1)}% diff\n`;
154
- });
155
- output += "\n";
156
- }
157
- if (noise.length > 0) {
158
- output += `**NOISE (${noise.length}):** ${noise.map((e) => e.name).join(", ")}\n\n`;
159
- }
160
- // Recommendation
161
- output += `### Recommendation\n\n`;
162
- if (critical.length > 0) {
163
- output += `Review ${critical.length} critical item(s) before approving. `;
164
- }
165
- if (review.length > 0) {
166
- output += `${review.length} item(s) need manual review. `;
167
- }
168
- if (expected.length + noise.length > 0 &&
169
- critical.length === 0 &&
170
- review.length === 0) {
171
- output += `All changes appear expected or are noise. Safe to approve.`;
172
- }
173
- output += "\n";
174
- }
175
- }
176
- // Add any sub-call errors
177
- if (errors.length > 0) {
178
- output += `\n### Partial Results\n\n`;
179
- errors.forEach((err) => {
180
- output += `- ${err}\n`;
181
- });
182
- }
183
- return { content: [{ type: "text", text: output }] };
184
- }
@@ -1,17 +0,0 @@
1
- /**
2
- * percy_run_tests — Execute a test command with Percy visual testing.
3
- *
4
- * Wraps any test command with `percy exec` to capture snapshots during tests.
5
- * Fire-and-forget: launches in background, returns immediately.
6
- *
7
- * Requires @percy/cli installed locally.
8
- */
9
- import { BrowserStackConfig } from "../../../lib/types.js";
10
- import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
11
- interface RunTestsArgs {
12
- project_name: string;
13
- test_command: string;
14
- type?: string;
15
- }
16
- export declare function percyRunTests(args: RunTestsArgs, config: BrowserStackConfig): Promise<CallToolResult>;
17
- export {};
@@ -1,107 +0,0 @@
1
- /**
2
- * percy_run_tests — Execute a test command with Percy visual testing.
3
- *
4
- * Wraps any test command with `percy exec` to capture snapshots during tests.
5
- * Fire-and-forget: launches in background, returns immediately.
6
- *
7
- * Requires @percy/cli installed locally.
8
- */
9
- import { execFile, spawn } from "child_process";
10
- import { promisify } from "util";
11
- const execFileAsync = promisify(execFile);
12
- async function getProjectToken(projectName, config) {
13
- const authString = `${config["browserstack-username"]}:${config["browserstack-access-key"]}`;
14
- const auth = Buffer.from(authString).toString("base64");
15
- const url = `https://api.browserstack.com/api/app_percy/get_project_token?name=${encodeURIComponent(projectName)}`;
16
- const response = await fetch(url, {
17
- headers: { Authorization: `Basic ${auth}` },
18
- });
19
- if (!response.ok)
20
- throw new Error(`Failed to get token for "${projectName}"`);
21
- const data = await response.json();
22
- if (!data?.token || !data?.success)
23
- throw new Error(`No token for "${projectName}"`);
24
- return data.token;
25
- }
26
- export async function percyRunTests(args, config) {
27
- const { project_name, test_command } = args;
28
- let output = `## Percy Test Run — Local Execution\n\n`;
29
- // Check Percy CLI
30
- try {
31
- await execFileAsync("npx", ["@percy/cli", "--version"]);
32
- }
33
- catch {
34
- output += `**Percy CLI not found.** Install it:\n\n`;
35
- output += `\`\`\`bash\nnpm install -g @percy/cli\n\`\`\`\n`;
36
- return { content: [{ type: "text", text: output }] };
37
- }
38
- // Get token
39
- let token;
40
- try {
41
- token = await getProjectToken(project_name, config);
42
- }
43
- catch (e) {
44
- return {
45
- content: [
46
- {
47
- type: "text",
48
- text: `Failed to get project token: ${e.message}`,
49
- },
50
- ],
51
- isError: true,
52
- };
53
- }
54
- output += `**Project:** ${project_name}\n`;
55
- output += `**Command:** \`${test_command}\`\n\n`;
56
- // Parse the test command into args
57
- const cmdParts = test_command.split(" ").filter(Boolean);
58
- // Spawn: npx @percy/cli exec -- <test_command>
59
- const child = spawn("npx", ["@percy/cli", "exec", "--", ...cmdParts], {
60
- env: { ...process.env, PERCY_TOKEN: token },
61
- stdio: ["ignore", "pipe", "pipe"],
62
- detached: true,
63
- shell: false,
64
- });
65
- let stdoutData = "";
66
- let buildUrl = "";
67
- child.stdout?.on("data", (data) => {
68
- const text = data.toString();
69
- stdoutData += text;
70
- const match = text.match(/https:\/\/percy\.io\/[^\s]+\/builds\/\d+/);
71
- if (match)
72
- buildUrl = match[0];
73
- });
74
- child.stderr?.on("data", (data) => {
75
- stdoutData += data.toString();
76
- });
77
- // Wait briefly for build URL
78
- await new Promise((resolve) => {
79
- const timeout = setTimeout(() => resolve(), 10000);
80
- child.on("close", () => {
81
- clearTimeout(timeout);
82
- resolve();
83
- });
84
- const check = setInterval(() => {
85
- if (buildUrl) {
86
- clearTimeout(timeout);
87
- clearInterval(check);
88
- resolve();
89
- }
90
- }, 500);
91
- });
92
- child.unref();
93
- if (buildUrl) {
94
- output += `**Build started!** Tests are running with Percy in the background.\n\n`;
95
- output += `**Build URL:** ${buildUrl}\n\n`;
96
- output += `Your tests are executing. Each \`percySnapshot()\` call in your tests captures a visual snapshot.\n`;
97
- output += `Results will appear at the build URL when tests complete.\n`;
98
- }
99
- else {
100
- const trimmed = stdoutData.trim().slice(0, 500);
101
- if (trimmed) {
102
- output += `**Percy output:**\n\`\`\`\n${trimmed}\n\`\`\`\n\n`;
103
- }
104
- output += `Tests are running in the background with Percy. Check your Percy dashboard for the build.\n`;
105
- }
106
- return { content: [{ type: "text", text: output }] };
107
- }
@@ -1,18 +0,0 @@
1
- /**
2
- * percy_snapshot_urls — Actually runs Percy CLI to snapshot URLs locally.
3
- *
4
- * Fire-and-forget: launches percy CLI in background, returns immediately
5
- * with build URL. User checks Percy dashboard for results.
6
- *
7
- * Requires @percy/cli installed locally (npx or global).
8
- */
9
- import { BrowserStackConfig } from "../../../lib/types.js";
10
- import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
11
- interface SnapshotUrlsArgs {
12
- project_name: string;
13
- urls: string;
14
- widths?: string;
15
- type?: string;
16
- }
17
- export declare function percySnapshotUrls(args: SnapshotUrlsArgs, config: BrowserStackConfig): Promise<CallToolResult>;
18
- export {};
@@ -1,197 +0,0 @@
1
- /**
2
- * percy_snapshot_urls — Actually runs Percy CLI to snapshot URLs locally.
3
- *
4
- * Fire-and-forget: launches percy CLI in background, returns immediately
5
- * with build URL. User checks Percy dashboard for results.
6
- *
7
- * Requires @percy/cli installed locally (npx or global).
8
- */
9
- import { execFile, spawn } from "child_process";
10
- import { promisify } from "util";
11
- import { writeFile, unlink, mkdtemp } from "fs/promises";
12
- import { join } from "path";
13
- import { tmpdir } from "os";
14
- const execFileAsync = promisify(execFile);
15
- // ── Helpers ─────────────────────────────────────────────────────────────────
16
- async function getProjectToken(projectName, config, type) {
17
- const authString = `${config["browserstack-username"]}:${config["browserstack-access-key"]}`;
18
- const auth = Buffer.from(authString).toString("base64");
19
- const params = new URLSearchParams({ name: projectName });
20
- if (type)
21
- params.append("type", type);
22
- const url = `https://api.browserstack.com/api/app_percy/get_project_token?${params.toString()}`;
23
- const response = await fetch(url, {
24
- headers: { Authorization: `Basic ${auth}` },
25
- });
26
- if (!response.ok)
27
- throw new Error(`Failed to get token for "${projectName}"`);
28
- const data = await response.json();
29
- if (!data?.token || !data?.success)
30
- throw new Error(`No token for "${projectName}"`);
31
- return data.token;
32
- }
33
- async function checkPercyCli() {
34
- // Check if @percy/cli is available
35
- try {
36
- const { stdout } = await execFileAsync("npx", ["@percy/cli", "--version"]);
37
- return stdout.trim();
38
- }
39
- catch {
40
- // Try global
41
- try {
42
- const { stdout } = await execFileAsync("percy", ["--version"]);
43
- return stdout.trim();
44
- }
45
- catch {
46
- return null;
47
- }
48
- }
49
- }
50
- export async function percySnapshotUrls(args, config) {
51
- const urls = args.urls
52
- .split(",")
53
- .map((u) => u.trim())
54
- .filter(Boolean);
55
- const widths = args.widths
56
- ? args.widths.split(",").map((w) => w.trim())
57
- : ["375", "1280"];
58
- if (urls.length === 0) {
59
- return {
60
- content: [{ type: "text", text: "No URLs provided." }],
61
- isError: true,
62
- };
63
- }
64
- let output = `## Percy Snapshot — Local Rendering\n\n`;
65
- // Step 1: Check Percy CLI
66
- const cliVersion = await checkPercyCli();
67
- if (!cliVersion) {
68
- output += `**Percy CLI not found.** Install it first:\n\n`;
69
- output += `\`\`\`bash\nnpm install -g @percy/cli\n\`\`\`\n\n`;
70
- output += `Or install locally: \`npm install --save-dev @percy/cli\`\n`;
71
- return { content: [{ type: "text", text: output }] };
72
- }
73
- output += `**Percy CLI:** ${cliVersion}\n`;
74
- // Step 2: Get project token
75
- let token;
76
- try {
77
- token = await getProjectToken(args.project_name, config, args.type);
78
- }
79
- catch (e) {
80
- return {
81
- content: [
82
- {
83
- type: "text",
84
- text: `Failed to get project token: ${e.message}`,
85
- },
86
- ],
87
- isError: true,
88
- };
89
- }
90
- output += `**Project:** ${args.project_name}\n`;
91
- output += `**URLs:** ${urls.length}\n`;
92
- output += `**Widths:** ${widths.join(", ")}px\n\n`;
93
- // Step 3: Create snapshots.yml config
94
- let yamlContent = "";
95
- urls.forEach((url, i) => {
96
- const name = urls.length === 1
97
- ? "Homepage"
98
- : url
99
- .replace(/^https?:\/\//, "")
100
- .replace(/[/:]/g, "-")
101
- .replace(/-+/g, "-")
102
- .replace(/^-|-$/g, "") || `Page ${i + 1}`;
103
- yamlContent += `- name: "${name}"\n`;
104
- yamlContent += ` url: ${url}\n`;
105
- yamlContent += ` waitForTimeout: 3000\n`;
106
- yamlContent += ` additionalSnapshots:\n`;
107
- widths.forEach((w) => {
108
- yamlContent += ` - width: ${w}\n`;
109
- });
110
- });
111
- // Write temp config file
112
- const tmpDir = await mkdtemp(join(tmpdir(), "percy-mcp-"));
113
- const configPath = join(tmpDir, "snapshots.yml");
114
- await writeFile(configPath, yamlContent, "utf-8");
115
- // Step 4: Launch Percy CLI in background
116
- output += `### Launching Percy snapshot...\n\n`;
117
- const env = {
118
- ...process.env,
119
- PERCY_TOKEN: token,
120
- };
121
- // Spawn percy CLI in background (fire and forget)
122
- const child = spawn("npx", ["@percy/cli", "snapshot", configPath], {
123
- env,
124
- stdio: ["ignore", "pipe", "pipe"],
125
- detached: true,
126
- });
127
- // Collect initial output for a few seconds
128
- let stdoutData = "";
129
- let stderrData = "";
130
- let buildUrl = "";
131
- child.stdout?.on("data", (data) => {
132
- const text = data.toString();
133
- stdoutData += text;
134
- // Try to extract build URL
135
- const match = text.match(/https:\/\/percy\.io\/[^\s]+\/builds\/\d+/);
136
- if (match)
137
- buildUrl = match[0];
138
- });
139
- child.stderr?.on("data", (data) => {
140
- stderrData += data.toString();
141
- });
142
- // Wait a few seconds for initial output (build creation)
143
- await new Promise((resolve) => {
144
- const timeout = setTimeout(() => resolve(), 8000);
145
- child.on("close", () => {
146
- clearTimeout(timeout);
147
- resolve();
148
- });
149
- // Also resolve if we find the build URL early
150
- const checkInterval = setInterval(() => {
151
- if (buildUrl) {
152
- clearTimeout(timeout);
153
- clearInterval(checkInterval);
154
- resolve();
155
- }
156
- }, 500);
157
- });
158
- // Unref so the process doesn't keep MCP server alive
159
- child.unref();
160
- // Clean up temp file after a delay
161
- setTimeout(async () => {
162
- try {
163
- await unlink(configPath);
164
- }
165
- catch {
166
- // ignore
167
- }
168
- }, 120000); // 2 minutes
169
- // Step 5: Report results
170
- if (buildUrl) {
171
- output += `**Build started!** Percy is rendering your pages in the background.\n\n`;
172
- output += `**Build URL:** ${buildUrl}\n\n`;
173
- output += `Percy is capturing ${urls.length} URL(s) at ${widths.length} width(s) = ${urls.length * widths.length} snapshot(s).\n\n`;
174
- output += `Check the build URL above for results (usually ready in 1-3 minutes).\n`;
175
- }
176
- else if (stdoutData || stderrData) {
177
- // No build URL found yet — show what we have
178
- const allOutput = (stdoutData + stderrData).trim();
179
- // Check for common errors
180
- if (allOutput.includes("not found") || allOutput.includes("ECONNREFUSED")) {
181
- output += `**Error:** The URL may not be reachable.\n\n`;
182
- output += `Make sure your app is running at the specified URL(s):\n`;
183
- urls.forEach((u) => {
184
- output += `- ${u}\n`;
185
- });
186
- output += `\n`;
187
- }
188
- output += `**Percy CLI output:**\n\`\`\`\n${allOutput.slice(0, 500)}\n\`\`\`\n\n`;
189
- output += `Percy is running in the background. If a build was created, check your Percy dashboard.\n`;
190
- }
191
- else {
192
- output += `**Percy CLI launched in background.** No output yet.\n\n`;
193
- output += `The build should appear in your Percy dashboard shortly.\n`;
194
- output += `Check: https://percy.io\n`;
195
- }
196
- return { content: [{ type: "text", text: output }] };
197
- }