@browserstack/mcp-server 1.2.14 → 1.2.15-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.
Files changed (170) hide show
  1. package/dist/lib/percy-api/auth.d.ts +41 -0
  2. package/dist/lib/percy-api/auth.js +96 -0
  3. package/dist/lib/percy-api/cache.d.ts +28 -0
  4. package/dist/lib/percy-api/cache.js +48 -0
  5. package/dist/lib/percy-api/client.d.ts +69 -0
  6. package/dist/lib/percy-api/client.js +275 -0
  7. package/dist/lib/percy-api/errors.d.ts +15 -0
  8. package/dist/lib/percy-api/errors.js +52 -0
  9. package/dist/lib/percy-api/formatter.d.ts +16 -0
  10. package/dist/lib/percy-api/formatter.js +344 -0
  11. package/dist/lib/percy-api/percy-auth.d.ts +43 -0
  12. package/dist/lib/percy-api/percy-auth.js +137 -0
  13. package/dist/lib/percy-api/percy-error-handler.d.ts +24 -0
  14. package/dist/lib/percy-api/percy-error-handler.js +302 -0
  15. package/dist/lib/percy-api/percy-session.d.ts +42 -0
  16. package/dist/lib/percy-api/percy-session.js +87 -0
  17. package/dist/lib/percy-api/polling.d.ts +26 -0
  18. package/dist/lib/percy-api/polling.js +42 -0
  19. package/dist/lib/percy-api/types.d.ts +56 -0
  20. package/dist/lib/percy-api/types.js +76 -0
  21. package/dist/server-factory.js +4 -0
  22. package/dist/tools/percy-mcp/advanced/branchline-operations.d.ts +16 -0
  23. package/dist/tools/percy-mcp/advanced/branchline-operations.js +81 -0
  24. package/dist/tools/percy-mcp/advanced/manage-variants.d.ts +16 -0
  25. package/dist/tools/percy-mcp/advanced/manage-variants.js +155 -0
  26. package/dist/tools/percy-mcp/advanced/manage-visual-monitoring.d.ts +16 -0
  27. package/dist/tools/percy-mcp/advanced/manage-visual-monitoring.js +171 -0
  28. package/dist/tools/percy-mcp/auth/auth-status.d.ts +3 -0
  29. package/dist/tools/percy-mcp/auth/auth-status.js +131 -0
  30. package/dist/tools/percy-mcp/core/approve-build.d.ts +14 -0
  31. package/dist/tools/percy-mcp/core/approve-build.js +97 -0
  32. package/dist/tools/percy-mcp/core/get-build-items.d.ts +13 -0
  33. package/dist/tools/percy-mcp/core/get-build-items.js +65 -0
  34. package/dist/tools/percy-mcp/core/get-build.d.ts +10 -0
  35. package/dist/tools/percy-mcp/core/get-build.js +16 -0
  36. package/dist/tools/percy-mcp/core/get-comparison.d.ts +11 -0
  37. package/dist/tools/percy-mcp/core/get-comparison.js +59 -0
  38. package/dist/tools/percy-mcp/core/get-snapshot.d.ts +10 -0
  39. package/dist/tools/percy-mcp/core/get-snapshot.js +40 -0
  40. package/dist/tools/percy-mcp/core/list-builds.d.ts +14 -0
  41. package/dist/tools/percy-mcp/core/list-builds.js +45 -0
  42. package/dist/tools/percy-mcp/core/list-projects.d.ts +12 -0
  43. package/dist/tools/percy-mcp/core/list-projects.js +51 -0
  44. package/dist/tools/percy-mcp/creation/create-app-snapshot.d.ts +12 -0
  45. package/dist/tools/percy-mcp/creation/create-app-snapshot.js +29 -0
  46. package/dist/tools/percy-mcp/creation/create-build.d.ts +19 -0
  47. package/dist/tools/percy-mcp/creation/create-build.js +68 -0
  48. package/dist/tools/percy-mcp/creation/create-comparison.d.ts +18 -0
  49. package/dist/tools/percy-mcp/creation/create-comparison.js +90 -0
  50. package/dist/tools/percy-mcp/creation/create-snapshot.d.ts +17 -0
  51. package/dist/tools/percy-mcp/creation/create-snapshot.js +99 -0
  52. package/dist/tools/percy-mcp/creation/finalize-build.d.ts +12 -0
  53. package/dist/tools/percy-mcp/creation/finalize-build.js +33 -0
  54. package/dist/tools/percy-mcp/creation/finalize-comparison.d.ts +10 -0
  55. package/dist/tools/percy-mcp/creation/finalize-comparison.js +16 -0
  56. package/dist/tools/percy-mcp/creation/finalize-snapshot.d.ts +12 -0
  57. package/dist/tools/percy-mcp/creation/finalize-snapshot.js +33 -0
  58. package/dist/tools/percy-mcp/creation/upload-resource.d.ts +15 -0
  59. package/dist/tools/percy-mcp/creation/upload-resource.js +43 -0
  60. package/dist/tools/percy-mcp/creation/upload-tile.d.ts +11 -0
  61. package/dist/tools/percy-mcp/creation/upload-tile.js +53 -0
  62. package/dist/tools/percy-mcp/diagnostics/analyze-logs-realtime.d.ts +13 -0
  63. package/dist/tools/percy-mcp/diagnostics/analyze-logs-realtime.js +65 -0
  64. package/dist/tools/percy-mcp/diagnostics/get-build-logs.d.ts +17 -0
  65. package/dist/tools/percy-mcp/diagnostics/get-build-logs.js +74 -0
  66. package/dist/tools/percy-mcp/diagnostics/get-network-logs.d.ts +5 -0
  67. package/dist/tools/percy-mcp/diagnostics/get-network-logs.js +21 -0
  68. package/dist/tools/percy-mcp/diagnostics/get-suggestions.d.ts +7 -0
  69. package/dist/tools/percy-mcp/diagnostics/get-suggestions.js +24 -0
  70. package/dist/tools/percy-mcp/index.d.ts +36 -0
  71. package/dist/tools/percy-mcp/index.js +1137 -0
  72. package/dist/tools/percy-mcp/intelligence/get-ai-analysis.d.ts +15 -0
  73. package/dist/tools/percy-mcp/intelligence/get-ai-analysis.js +166 -0
  74. package/dist/tools/percy-mcp/intelligence/get-ai-quota.d.ts +9 -0
  75. package/dist/tools/percy-mcp/intelligence/get-ai-quota.js +73 -0
  76. package/dist/tools/percy-mcp/intelligence/get-build-summary.d.ts +11 -0
  77. package/dist/tools/percy-mcp/intelligence/get-build-summary.js +78 -0
  78. package/dist/tools/percy-mcp/intelligence/get-rca.d.ts +6 -0
  79. package/dist/tools/percy-mcp/intelligence/get-rca.js +153 -0
  80. package/dist/tools/percy-mcp/intelligence/suggest-prompt.d.ts +15 -0
  81. package/dist/tools/percy-mcp/intelligence/suggest-prompt.js +86 -0
  82. package/dist/tools/percy-mcp/intelligence/trigger-ai-recompute.d.ts +16 -0
  83. package/dist/tools/percy-mcp/intelligence/trigger-ai-recompute.js +64 -0
  84. package/dist/tools/percy-mcp/management/create-project.d.ts +14 -0
  85. package/dist/tools/percy-mcp/management/create-project.js +52 -0
  86. package/dist/tools/percy-mcp/management/get-usage-stats.d.ts +12 -0
  87. package/dist/tools/percy-mcp/management/get-usage-stats.js +61 -0
  88. package/dist/tools/percy-mcp/management/manage-browser-targets.d.ts +12 -0
  89. package/dist/tools/percy-mcp/management/manage-browser-targets.js +136 -0
  90. package/dist/tools/percy-mcp/management/manage-comments.d.ts +14 -0
  91. package/dist/tools/percy-mcp/management/manage-comments.js +147 -0
  92. package/dist/tools/percy-mcp/management/manage-ignored-regions.d.ts +18 -0
  93. package/dist/tools/percy-mcp/management/manage-ignored-regions.js +182 -0
  94. package/dist/tools/percy-mcp/management/manage-project-settings.d.ts +16 -0
  95. package/dist/tools/percy-mcp/management/manage-project-settings.js +97 -0
  96. package/dist/tools/percy-mcp/management/manage-tokens.d.ts +14 -0
  97. package/dist/tools/percy-mcp/management/manage-tokens.js +90 -0
  98. package/dist/tools/percy-mcp/management/manage-webhooks.d.ts +15 -0
  99. package/dist/tools/percy-mcp/management/manage-webhooks.js +180 -0
  100. package/dist/tools/percy-mcp/v2/auth-status.d.ts +3 -0
  101. package/dist/tools/percy-mcp/v2/auth-status.js +80 -0
  102. package/dist/tools/percy-mcp/v2/clone-build.d.ts +24 -0
  103. package/dist/tools/percy-mcp/v2/clone-build.js +539 -0
  104. package/dist/tools/percy-mcp/v2/create-app-build.d.ts +28 -0
  105. package/dist/tools/percy-mcp/v2/create-app-build.js +442 -0
  106. package/dist/tools/percy-mcp/v2/create-build.d.ts +16 -0
  107. package/dist/tools/percy-mcp/v2/create-build.js +601 -0
  108. package/dist/tools/percy-mcp/v2/create-project.d.ts +8 -0
  109. package/dist/tools/percy-mcp/v2/create-project.js +33 -0
  110. package/dist/tools/percy-mcp/v2/discover-urls.d.ts +7 -0
  111. package/dist/tools/percy-mcp/v2/discover-urls.js +38 -0
  112. package/dist/tools/percy-mcp/v2/figma-baseline.d.ts +7 -0
  113. package/dist/tools/percy-mcp/v2/figma-baseline.js +18 -0
  114. package/dist/tools/percy-mcp/v2/figma-build.d.ts +7 -0
  115. package/dist/tools/percy-mcp/v2/figma-build.js +39 -0
  116. package/dist/tools/percy-mcp/v2/figma-link.d.ts +6 -0
  117. package/dist/tools/percy-mcp/v2/figma-link.js +27 -0
  118. package/dist/tools/percy-mcp/v2/get-ai-summary.d.ts +5 -0
  119. package/dist/tools/percy-mcp/v2/get-ai-summary.js +109 -0
  120. package/dist/tools/percy-mcp/v2/get-build-detail.d.ts +22 -0
  121. package/dist/tools/percy-mcp/v2/get-build-detail.js +567 -0
  122. package/dist/tools/percy-mcp/v2/get-builds.d.ts +8 -0
  123. package/dist/tools/percy-mcp/v2/get-builds.js +63 -0
  124. package/dist/tools/percy-mcp/v2/get-comparison.d.ts +5 -0
  125. package/dist/tools/percy-mcp/v2/get-comparison.js +94 -0
  126. package/dist/tools/percy-mcp/v2/get-devices.d.ts +5 -0
  127. package/dist/tools/percy-mcp/v2/get-devices.js +33 -0
  128. package/dist/tools/percy-mcp/v2/get-insights.d.ts +7 -0
  129. package/dist/tools/percy-mcp/v2/get-insights.js +52 -0
  130. package/dist/tools/percy-mcp/v2/get-projects.d.ts +6 -0
  131. package/dist/tools/percy-mcp/v2/get-projects.js +41 -0
  132. package/dist/tools/percy-mcp/v2/get-snapshot.d.ts +5 -0
  133. package/dist/tools/percy-mcp/v2/get-snapshot.js +96 -0
  134. package/dist/tools/percy-mcp/v2/get-test-case-history.d.ts +5 -0
  135. package/dist/tools/percy-mcp/v2/get-test-case-history.js +20 -0
  136. package/dist/tools/percy-mcp/v2/get-test-cases.d.ts +6 -0
  137. package/dist/tools/percy-mcp/v2/get-test-cases.js +36 -0
  138. package/dist/tools/percy-mcp/v2/index.d.ts +35 -0
  139. package/dist/tools/percy-mcp/v2/index.js +544 -0
  140. package/dist/tools/percy-mcp/v2/list-integrations.d.ts +5 -0
  141. package/dist/tools/percy-mcp/v2/list-integrations.js +41 -0
  142. package/dist/tools/percy-mcp/v2/manage-domains.d.ts +8 -0
  143. package/dist/tools/percy-mcp/v2/manage-domains.js +33 -0
  144. package/dist/tools/percy-mcp/v2/manage-insights-email.d.ts +8 -0
  145. package/dist/tools/percy-mcp/v2/manage-insights-email.js +49 -0
  146. package/dist/tools/percy-mcp/v2/manage-usage-alerts.d.ts +10 -0
  147. package/dist/tools/percy-mcp/v2/manage-usage-alerts.js +43 -0
  148. package/dist/tools/percy-mcp/v2/migrate-integrations.d.ts +6 -0
  149. package/dist/tools/percy-mcp/v2/migrate-integrations.js +20 -0
  150. package/dist/tools/percy-mcp/v2/preview-comparison.d.ts +5 -0
  151. package/dist/tools/percy-mcp/v2/preview-comparison.js +17 -0
  152. package/dist/tools/percy-mcp/v2/search-build-items.d.ts +12 -0
  153. package/dist/tools/percy-mcp/v2/search-build-items.js +45 -0
  154. package/dist/tools/percy-mcp/workflows/auto-triage.d.ts +7 -0
  155. package/dist/tools/percy-mcp/workflows/auto-triage.js +82 -0
  156. package/dist/tools/percy-mcp/workflows/clone-build.d.ts +22 -0
  157. package/dist/tools/percy-mcp/workflows/clone-build.js +414 -0
  158. package/dist/tools/percy-mcp/workflows/create-percy-build.d.ts +32 -0
  159. package/dist/tools/percy-mcp/workflows/create-percy-build.js +434 -0
  160. package/dist/tools/percy-mcp/workflows/debug-failed-build.d.ts +5 -0
  161. package/dist/tools/percy-mcp/workflows/debug-failed-build.js +122 -0
  162. package/dist/tools/percy-mcp/workflows/diff-explain.d.ts +6 -0
  163. package/dist/tools/percy-mcp/workflows/diff-explain.js +147 -0
  164. package/dist/tools/percy-mcp/workflows/pr-visual-report.d.ts +8 -0
  165. package/dist/tools/percy-mcp/workflows/pr-visual-report.js +184 -0
  166. package/dist/tools/percy-mcp/workflows/run-tests.d.ts +17 -0
  167. package/dist/tools/percy-mcp/workflows/run-tests.js +107 -0
  168. package/dist/tools/percy-mcp/workflows/snapshot-urls.d.ts +18 -0
  169. package/dist/tools/percy-mcp/workflows/snapshot-urls.js +197 -0
  170. package/package.json +4 -3
@@ -0,0 +1,15 @@
1
+ /**
2
+ * percy_get_ai_analysis — Get AI-powered visual diff analysis.
3
+ *
4
+ * Two modes:
5
+ * 1. Single comparison (comparison_id) — regions, diff ratios, bug flags
6
+ * 2. Build aggregate (build_id) — overall AI metrics and job status
7
+ */
8
+ import { BrowserStackConfig } from "../../../lib/types.js";
9
+ import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
10
+ interface GetAiAnalysisArgs {
11
+ comparison_id?: string;
12
+ build_id?: string;
13
+ }
14
+ export declare function percyGetAiAnalysis(args: GetAiAnalysisArgs, config: BrowserStackConfig): Promise<CallToolResult>;
15
+ export {};
@@ -0,0 +1,166 @@
1
+ /**
2
+ * percy_get_ai_analysis — Get AI-powered visual diff analysis.
3
+ *
4
+ * Two modes:
5
+ * 1. Single comparison (comparison_id) — regions, diff ratios, bug flags
6
+ * 2. Build aggregate (build_id) — overall AI metrics and job status
7
+ */
8
+ import { PercyClient } from "../../../lib/percy-api/client.js";
9
+ function pct(value) {
10
+ if (value == null)
11
+ return "N/A";
12
+ return `${(value * 100).toFixed(1)}%`;
13
+ }
14
+ function na(value) {
15
+ if (value == null || value === "")
16
+ return "N/A";
17
+ return String(value);
18
+ }
19
+ // ---------------------------------------------------------------------------
20
+ // Single-comparison AI analysis
21
+ // ---------------------------------------------------------------------------
22
+ async function analyzeComparison(comparisonId, client) {
23
+ const includes = [
24
+ "head-screenshot.image",
25
+ "base-screenshot.image",
26
+ "diff-image",
27
+ "ai-diff-image",
28
+ "browser.browser-family",
29
+ "comparison-tag",
30
+ ];
31
+ const response = await client.get(`/comparisons/${comparisonId}`, undefined, includes);
32
+ const comparison = response.data;
33
+ if (!comparison) {
34
+ return {
35
+ content: [
36
+ {
37
+ type: "text",
38
+ text: `_Comparison ${comparisonId} not found._`,
39
+ },
40
+ ],
41
+ };
42
+ }
43
+ const lines = [];
44
+ lines.push(`## AI Analysis — Comparison #${comparisonId}`);
45
+ lines.push("");
46
+ // Diff ratios
47
+ const aiDiff = comparison.aiDiffRatio;
48
+ const rawDiff = comparison.diffRatio;
49
+ if (aiDiff != null || rawDiff != null) {
50
+ lines.push(`**AI Diff Ratio:** ${pct(aiDiff)} (raw: ${pct(rawDiff)})`);
51
+ }
52
+ // AI processing state
53
+ if (comparison.aiProcessingState &&
54
+ comparison.aiProcessingState !== "completed") {
55
+ lines.push(`> ⚠ AI processing state: ${comparison.aiProcessingState}. Results may be incomplete.`);
56
+ lines.push("");
57
+ }
58
+ // Bug count from regions
59
+ const regions = comparison.appliedRegions ?? [];
60
+ const bugCount = regions.filter((r) => r.isBug === true || r.classification === "bug" || r.type === "bug").length;
61
+ if (bugCount > 0) {
62
+ lines.push(`**Potential Bugs:** ${bugCount}`);
63
+ }
64
+ // Regions
65
+ if (regions.length > 0) {
66
+ lines.push("");
67
+ lines.push(`### Regions (${regions.length}):`);
68
+ for (let i = 0; i < regions.length; i++) {
69
+ const region = regions[i];
70
+ const label = na(region.label ?? region.name);
71
+ const type = region.type ?? region.changeType ?? "unknown";
72
+ const desc = region.description ?? "";
73
+ const ignored = region.ignored === true || region.state === "ignored";
74
+ let line;
75
+ if (ignored) {
76
+ line = `${i + 1}. ~~${label}~~ (ignored by AI)`;
77
+ }
78
+ else {
79
+ line = `${i + 1}. **${label}** (${type})`;
80
+ }
81
+ if (desc)
82
+ line += `\n ${desc}`;
83
+ lines.push(line);
84
+ }
85
+ }
86
+ else {
87
+ lines.push("");
88
+ lines.push("_No AI regions detected for this comparison._");
89
+ }
90
+ return {
91
+ content: [{ type: "text", text: lines.join("\n") }],
92
+ };
93
+ }
94
+ // ---------------------------------------------------------------------------
95
+ // Build-aggregate AI analysis
96
+ // ---------------------------------------------------------------------------
97
+ async function analyzeBuild(buildId, client) {
98
+ const response = await client.get(`/builds/${buildId}`, { "include-metadata": "true" });
99
+ const build = response.data;
100
+ if (!build) {
101
+ return {
102
+ content: [{ type: "text", text: `_Build ${buildId} not found._` }],
103
+ };
104
+ }
105
+ const ai = build.aiDetails;
106
+ if (!ai) {
107
+ return {
108
+ content: [
109
+ {
110
+ type: "text",
111
+ text: "AI analysis is not enabled for this project.",
112
+ },
113
+ ],
114
+ };
115
+ }
116
+ const lines = [];
117
+ lines.push(`## AI Analysis — Build #${build.buildNumber ?? buildId}`);
118
+ lines.push("");
119
+ if (ai.comparisonsAnalyzed != null) {
120
+ lines.push(`- Comparisons analyzed: ${ai.comparisonsAnalyzed}`);
121
+ }
122
+ if (ai.potentialBugs != null) {
123
+ lines.push(`- Potential bugs: ${ai.potentialBugs}`);
124
+ }
125
+ if (ai.totalAiDiffs != null) {
126
+ lines.push(`- Total AI visual diffs: ${ai.totalAiDiffs}`);
127
+ }
128
+ if (ai.diffReduction != null) {
129
+ lines.push(`- Diff reduction: ${ai.diffReduction} diffs filtered`);
130
+ }
131
+ else if (ai.originalDiffPercent != null && ai.aiDiffPercent != null) {
132
+ lines.push(`- Diff reduction: ${pct(ai.originalDiffPercent)} → ${pct(ai.aiDiffPercent)}`);
133
+ }
134
+ const jobsCompleted = ai.aiJobsCompleted != null ? (ai.aiJobsCompleted ? "yes" : "no") : "N/A";
135
+ lines.push(`- AI jobs completed: ${jobsCompleted}`);
136
+ const summaryStatus = na(ai.summaryStatus ?? ai.aiSummaryStatus);
137
+ lines.push(`- Summary status: ${summaryStatus}`);
138
+ // Warning if AI is still processing
139
+ if (ai.aiJobsCompleted === false || ai.summaryStatus === "processing") {
140
+ lines.push("");
141
+ lines.push("> ⚠ AI analysis is still in progress. Some metrics may be incomplete. Re-run for final results.");
142
+ }
143
+ return {
144
+ content: [{ type: "text", text: lines.join("\n") }],
145
+ };
146
+ }
147
+ // ---------------------------------------------------------------------------
148
+ // Entry point
149
+ // ---------------------------------------------------------------------------
150
+ export async function percyGetAiAnalysis(args, config) {
151
+ if (!args.comparison_id && !args.build_id) {
152
+ return {
153
+ content: [
154
+ {
155
+ type: "text",
156
+ text: "_Error: Provide either `comparison_id` or `build_id` for AI analysis._",
157
+ },
158
+ ],
159
+ };
160
+ }
161
+ const client = new PercyClient(config, { scope: "project" });
162
+ if (args.comparison_id) {
163
+ return analyzeComparison(args.comparison_id, client);
164
+ }
165
+ return analyzeBuild(args.build_id, client);
166
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * percy_get_ai_quota — Check Percy AI quota status.
3
+ *
4
+ * Since there is no direct quota endpoint, derives AI quota info
5
+ * from the latest build's AI details.
6
+ */
7
+ import { BrowserStackConfig } from "../../../lib/types.js";
8
+ import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
9
+ export declare function percyGetAiQuota(_args: Record<string, never>, config: BrowserStackConfig): Promise<CallToolResult>;
@@ -0,0 +1,73 @@
1
+ /**
2
+ * percy_get_ai_quota — Check Percy AI quota status.
3
+ *
4
+ * Since there is no direct quota endpoint, derives AI quota info
5
+ * from the latest build's AI details.
6
+ */
7
+ import { PercyClient } from "../../../lib/percy-api/client.js";
8
+ export async function percyGetAiQuota(_args, config) {
9
+ const client = new PercyClient(config, { scope: "project" });
10
+ // Fetch the latest build to extract AI details
11
+ const response = await client.get("/builds", {
12
+ "page[limit]": "1",
13
+ "include-metadata": "true",
14
+ });
15
+ const builds = Array.isArray(response.data) ? response.data : [];
16
+ if (builds.length === 0) {
17
+ return {
18
+ content: [
19
+ {
20
+ type: "text",
21
+ text: "AI quota information unavailable. No builds found for this project.",
22
+ },
23
+ ],
24
+ };
25
+ }
26
+ const build = builds[0];
27
+ const ai = build.aiDetails;
28
+ if (!ai) {
29
+ return {
30
+ content: [
31
+ {
32
+ type: "text",
33
+ text: "AI quota information unavailable. Ensure AI is enabled on your Percy project.",
34
+ },
35
+ ],
36
+ };
37
+ }
38
+ const lines = [];
39
+ lines.push("## Percy AI Quota Status");
40
+ lines.push("");
41
+ // Quota / regeneration info
42
+ const used = ai.regenerationsUsed ?? ai.quotaUsed;
43
+ const total = ai.regenerationsTotal ?? ai.quotaTotal ?? ai.dailyQuota;
44
+ const plan = ai.planType ?? ai.plan ?? ai.tier;
45
+ if (used != null && total != null) {
46
+ lines.push(`**Daily Regenerations:** ${used} / ${total} used`);
47
+ }
48
+ else if (total != null) {
49
+ lines.push(`**Daily Regeneration Limit:** ${total}`);
50
+ }
51
+ else {
52
+ lines.push("**Daily Regenerations:** Quota details not available in build metadata.");
53
+ }
54
+ if (plan) {
55
+ lines.push(`**Plan:** ${plan}`);
56
+ }
57
+ // Additional AI stats from the latest build
58
+ if (ai.comparisonsAnalyzed != null) {
59
+ lines.push("");
60
+ lines.push("### Latest Build AI Stats");
61
+ lines.push(`- Build #${build.buildNumber ?? build.id}`);
62
+ lines.push(`- Comparisons analyzed: ${ai.comparisonsAnalyzed}`);
63
+ if (ai.potentialBugs != null) {
64
+ lines.push(`- Potential bugs detected: ${ai.potentialBugs}`);
65
+ }
66
+ if (ai.aiJobsCompleted != null) {
67
+ lines.push(`- AI jobs completed: ${ai.aiJobsCompleted ? "yes" : "no"}`);
68
+ }
69
+ }
70
+ return {
71
+ content: [{ type: "text", text: lines.join("\n") }],
72
+ };
73
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * percy_get_build_summary — Get AI-generated natural language summary
3
+ * of all visual changes in a Percy build.
4
+ */
5
+ import { BrowserStackConfig } from "../../../lib/types.js";
6
+ import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
7
+ interface GetBuildSummaryArgs {
8
+ build_id: string;
9
+ }
10
+ export declare function percyGetBuildSummary(args: GetBuildSummaryArgs, config: BrowserStackConfig): Promise<CallToolResult>;
11
+ export {};
@@ -0,0 +1,78 @@
1
+ /**
2
+ * percy_get_build_summary — Get AI-generated natural language summary
3
+ * of all visual changes in a Percy build.
4
+ */
5
+ import { PercyClient } from "../../../lib/percy-api/client.js";
6
+ export async function percyGetBuildSummary(args, config) {
7
+ const client = new PercyClient(config, { scope: "project" });
8
+ const response = await client.get(`/builds/${args.build_id}`, { "include-metadata": "true" }, [
9
+ "build-summary",
10
+ ]);
11
+ const build = response.data;
12
+ if (!build) {
13
+ return {
14
+ content: [{ type: "text", text: `_Build ${args.build_id} not found._` }],
15
+ };
16
+ }
17
+ // Check for build summary in relationships or top-level
18
+ const summary = build.buildSummary?.content ??
19
+ build.buildSummary?.summary ??
20
+ build.summary ??
21
+ null;
22
+ if (summary && typeof summary === "string") {
23
+ const lines = [];
24
+ lines.push(`## Build Summary — Build #${build.buildNumber ?? args.build_id}`);
25
+ lines.push("");
26
+ // The summary may be a JSON string or plain text
27
+ let parsedSummary;
28
+ try {
29
+ const parsed = JSON.parse(summary);
30
+ // If it parsed as an object, format its contents
31
+ if (typeof parsed === "object" && parsed !== null) {
32
+ parsedSummary = Object.entries(parsed)
33
+ .map(([key, value]) => `**${key}:** ${value}`)
34
+ .join("\n");
35
+ }
36
+ else {
37
+ parsedSummary = String(parsed);
38
+ }
39
+ }
40
+ catch {
41
+ // Plain text — use as-is
42
+ parsedSummary = summary;
43
+ }
44
+ lines.push(parsedSummary);
45
+ return {
46
+ content: [{ type: "text", text: lines.join("\n") }],
47
+ };
48
+ }
49
+ // No summary — check AI details for reason
50
+ const ai = build.aiDetails;
51
+ if (ai) {
52
+ const status = ai.summaryStatus ?? ai.aiSummaryStatus;
53
+ if (status === "processing") {
54
+ return {
55
+ content: [
56
+ {
57
+ type: "text",
58
+ text: "Build summary is being generated. Try again in a minute.",
59
+ },
60
+ ],
61
+ };
62
+ }
63
+ if (status === "skipped") {
64
+ const reason = ai.summaryReason ?? ai.summarySkipReason ?? "unknown reason";
65
+ return {
66
+ content: [
67
+ {
68
+ type: "text",
69
+ text: `Build summary unavailable. Reason: ${reason}`,
70
+ },
71
+ ],
72
+ };
73
+ }
74
+ }
75
+ return {
76
+ content: [{ type: "text", text: "No build summary available." }],
77
+ };
78
+ }
@@ -0,0 +1,6 @@
1
+ import { BrowserStackConfig } from "../../../lib/types.js";
2
+ import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
3
+ export declare function percyGetRca(args: {
4
+ comparison_id: string;
5
+ trigger_if_missing?: boolean;
6
+ }, config: BrowserStackConfig): Promise<CallToolResult>;
@@ -0,0 +1,153 @@
1
+ import { PercyClient } from "../../../lib/percy-api/client.js";
2
+ import { pollUntil } from "../../../lib/percy-api/polling.js";
3
+ export async function percyGetRca(args, config) {
4
+ const client = new PercyClient(config);
5
+ const triggerIfMissing = args.trigger_if_missing !== false; // default true
6
+ // Step 1: Check existing RCA status
7
+ // GET /rca?comparison_id={id}
8
+ // Response has: status (pending/finished/failed), diffNodes (when finished)
9
+ let rcaData;
10
+ try {
11
+ rcaData = await client.get("/rca", { comparison_id: args.comparison_id });
12
+ }
13
+ catch (e) {
14
+ // 404 means RCA not started
15
+ if (e.statusCode === 404 && triggerIfMissing) {
16
+ // Step 2: Trigger RCA
17
+ try {
18
+ await client.post("/rca", {
19
+ data: {
20
+ type: "rca",
21
+ attributes: { "comparison-id": args.comparison_id },
22
+ },
23
+ });
24
+ }
25
+ catch (triggerError) {
26
+ if (triggerError.statusCode === 422) {
27
+ return {
28
+ content: [
29
+ {
30
+ type: "text",
31
+ text: "RCA requires DOM metadata. This comparison type does not support RCA.",
32
+ },
33
+ ],
34
+ isError: true,
35
+ };
36
+ }
37
+ throw triggerError;
38
+ }
39
+ rcaData = { status: "pending" };
40
+ }
41
+ else if (e.statusCode === 404) {
42
+ return {
43
+ content: [
44
+ {
45
+ type: "text",
46
+ text: "RCA not yet triggered for this comparison. Set trigger_if_missing=true to start it.",
47
+ },
48
+ ],
49
+ };
50
+ }
51
+ else {
52
+ throw e;
53
+ }
54
+ }
55
+ // Step 3: Poll if pending
56
+ if (rcaData?.status === "pending") {
57
+ const result = await pollUntil(async () => {
58
+ const data = await client.get("/rca", {
59
+ comparison_id: args.comparison_id,
60
+ });
61
+ if (data?.status === "finished")
62
+ return { done: true, result: data };
63
+ if (data?.status === "failed")
64
+ return { done: true, result: data };
65
+ return { done: false };
66
+ }, { initialDelayMs: 500, maxDelayMs: 5000, maxTimeoutMs: 120000 });
67
+ if (!result) {
68
+ return {
69
+ content: [
70
+ {
71
+ type: "text",
72
+ text: "RCA analysis timed out after 2 minutes. The analysis may still be processing — try again later.",
73
+ },
74
+ ],
75
+ };
76
+ }
77
+ rcaData = result;
78
+ }
79
+ if (rcaData?.status === "failed") {
80
+ return {
81
+ content: [
82
+ {
83
+ type: "text",
84
+ text: "RCA analysis failed. The comparison may not have sufficient DOM metadata.",
85
+ },
86
+ ],
87
+ isError: true,
88
+ };
89
+ }
90
+ // Step 4: Format diff nodes
91
+ const diffNodes = rcaData?.diffNodes || rcaData?.diff_nodes || {};
92
+ const commonDiffs = diffNodes.common_diffs || [];
93
+ const extraBase = diffNodes.extra_base || [];
94
+ const extraHead = diffNodes.extra_head || [];
95
+ let output = `## Root Cause Analysis — Comparison #${args.comparison_id}\n\n`;
96
+ output += `**Status:** ${rcaData?.status || "unknown"}\n\n`;
97
+ if (commonDiffs.length > 0) {
98
+ output += `### Changed Elements (${commonDiffs.length})\n\n`;
99
+ commonDiffs.forEach((diff, i) => {
100
+ const base = diff.base || {};
101
+ const head = diff.head || {};
102
+ const tag = head.tagName || base.tagName || "unknown";
103
+ const xpath = head.xpath || base.xpath || "";
104
+ const diffType = head.diff_type === 1
105
+ ? "DIFF"
106
+ : head.diff_type === 2
107
+ ? "IGNORED"
108
+ : "unknown";
109
+ output += `${i + 1}. **${tag}** (${diffType})\n`;
110
+ if (xpath)
111
+ output += ` XPath: \`${xpath}\`\n`;
112
+ // Show attribute differences
113
+ const baseAttrs = base.attributes || {};
114
+ const headAttrs = head.attributes || {};
115
+ const allKeys = new Set([
116
+ ...Object.keys(baseAttrs),
117
+ ...Object.keys(headAttrs),
118
+ ]);
119
+ for (const key of allKeys) {
120
+ if (JSON.stringify(baseAttrs[key]) !== JSON.stringify(headAttrs[key])) {
121
+ output += ` ${key}: \`${baseAttrs[key] ?? "N/A"}\` → \`${headAttrs[key] ?? "N/A"}\`\n`;
122
+ }
123
+ }
124
+ output += "\n";
125
+ });
126
+ }
127
+ if (extraBase.length > 0) {
128
+ output += `### Removed Elements (${extraBase.length})\n\n`;
129
+ extraBase.forEach((node, i) => {
130
+ const detail = node.node_detail || node;
131
+ output += `${i + 1}. **${detail.tagName || "unknown"}** — removed from head\n`;
132
+ if (detail.xpath)
133
+ output += ` XPath: \`${detail.xpath}\`\n`;
134
+ output += "\n";
135
+ });
136
+ }
137
+ if (extraHead.length > 0) {
138
+ output += `### Added Elements (${extraHead.length})\n\n`;
139
+ extraHead.forEach((node, i) => {
140
+ const detail = node.node_detail || node;
141
+ output += `${i + 1}. **${detail.tagName || "unknown"}** — added in head\n`;
142
+ if (detail.xpath)
143
+ output += ` XPath: \`${detail.xpath}\`\n`;
144
+ output += "\n";
145
+ });
146
+ }
147
+ if (commonDiffs.length === 0 &&
148
+ extraBase.length === 0 &&
149
+ extraHead.length === 0) {
150
+ output += "No DOM differences found.\n";
151
+ }
152
+ return { content: [{ type: "text", text: output }] };
153
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * percy_suggest_prompt — Get an AI-generated prompt suggestion for diff regions.
3
+ *
4
+ * Sends region IDs to the Percy API, polls for the suggestion result,
5
+ * and returns the generated prompt text.
6
+ */
7
+ import { BrowserStackConfig } from "../../../lib/types.js";
8
+ import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
9
+ interface SuggestPromptArgs {
10
+ comparison_id: string;
11
+ region_ids: string;
12
+ ignore_change?: boolean;
13
+ }
14
+ export declare function percySuggestPrompt(args: SuggestPromptArgs, config: BrowserStackConfig): Promise<CallToolResult>;
15
+ export {};
@@ -0,0 +1,86 @@
1
+ /**
2
+ * percy_suggest_prompt — Get an AI-generated prompt suggestion for diff regions.
3
+ *
4
+ * Sends region IDs to the Percy API, polls for the suggestion result,
5
+ * and returns the generated prompt text.
6
+ */
7
+ import { PercyClient } from "../../../lib/percy-api/client.js";
8
+ import { pollUntil } from "../../../lib/percy-api/polling.js";
9
+ export async function percySuggestPrompt(args, config) {
10
+ const client = new PercyClient(config);
11
+ const regionIds = args.region_ids
12
+ .split(",")
13
+ .map((id) => id.trim())
14
+ .filter(Boolean);
15
+ const regionTypes = regionIds.map(() => "ai_region");
16
+ const body = {
17
+ data: {
18
+ attributes: {
19
+ "comparison-id": parseInt(args.comparison_id, 10),
20
+ "region-id": regionIds.map(Number),
21
+ "region-type": regionTypes,
22
+ "ignore-change": args.ignore_change !== false,
23
+ },
24
+ },
25
+ };
26
+ const result = await client.post("/suggest-prompt", body);
27
+ const identifier = result?.identifier ||
28
+ result?.id;
29
+ if (!identifier) {
30
+ return {
31
+ content: [
32
+ {
33
+ type: "text",
34
+ text: "Prompt suggestion initiated but no tracking ID received. Check results manually.",
35
+ },
36
+ ],
37
+ };
38
+ }
39
+ const suggestion = await pollUntil(async () => {
40
+ const status = await client.get("/job_status", {
41
+ sync: "true",
42
+ type: "ai",
43
+ id: String(identifier),
44
+ });
45
+ const entry = status?.[String(identifier)];
46
+ if (entry?.status === true) {
47
+ return { done: true, result: entry };
48
+ }
49
+ if (entry?.error) {
50
+ return { done: true, result: entry };
51
+ }
52
+ return { done: false };
53
+ }, { initialDelayMs: 1000, maxDelayMs: 3000, maxTimeoutMs: 30000 });
54
+ if (!suggestion) {
55
+ return {
56
+ content: [
57
+ {
58
+ type: "text",
59
+ text: "Prompt suggestion timed out. The AI is still generating — try again in a moment.",
60
+ },
61
+ ],
62
+ };
63
+ }
64
+ if (suggestion.error) {
65
+ return {
66
+ content: [
67
+ {
68
+ type: "text",
69
+ text: `Prompt suggestion failed: ${suggestion.error}`,
70
+ },
71
+ ],
72
+ isError: true,
73
+ };
74
+ }
75
+ const data = suggestion.data;
76
+ const prompt = data?.generated_prompt ||
77
+ suggestion.generated_prompt ||
78
+ "No prompt generated";
79
+ let output = "## AI Prompt Suggestion\n\n";
80
+ output += `**Suggested prompt:** ${prompt}\n\n`;
81
+ output += `**Mode:** ${args.ignore_change !== false ? "ignore" : "show"}\n`;
82
+ output += `**Regions analyzed:** ${regionIds.length}\n\n`;
83
+ output +=
84
+ "Use this prompt with `percy_trigger_ai_recompute` to apply it across all comparisons.\n";
85
+ return { content: [{ type: "text", text: output }] };
86
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * percy_trigger_ai_recompute — Re-run Percy AI analysis with a custom prompt.
3
+ *
4
+ * Sends a recompute request for a build or single comparison, optionally
5
+ * with a user-supplied prompt and ignore/unignore mode.
6
+ */
7
+ import { BrowserStackConfig } from "../../../lib/types.js";
8
+ import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
9
+ interface TriggerAiRecomputeArgs {
10
+ build_id?: string;
11
+ comparison_id?: string;
12
+ prompt?: string;
13
+ mode?: string;
14
+ }
15
+ export declare function percyTriggerAiRecompute(args: TriggerAiRecomputeArgs, config: BrowserStackConfig): Promise<CallToolResult>;
16
+ export {};