@browserstack/mcp-server 1.2.15-beta.1 → 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 +3 -2
@@ -0,0 +1,94 @@
1
+ import { percyGet } from "../../../lib/percy-api/percy-auth.js";
2
+ export async function percyGetComparison(args, config) {
3
+ const response = await percyGet(`/comparisons/${args.comparison_id}`, config, {
4
+ include: [
5
+ "head-screenshot.image",
6
+ "base-screenshot.image",
7
+ "diff-image",
8
+ "ai-diff-image",
9
+ "browser.browser-family",
10
+ "comparison-tag",
11
+ ].join(","),
12
+ });
13
+ const comp = response?.data || {};
14
+ const attrs = comp.attributes || {};
15
+ const included = response?.included || [];
16
+ const ai = attrs["ai-details"] || {};
17
+ // Resolve browser name
18
+ const browserId = comp.relationships?.browser?.data?.id;
19
+ const browser = included.find((i) => i.type === "browsers" && i.id === browserId);
20
+ const familyId = browser?.relationships?.["browser-family"]?.data?.id;
21
+ const family = included.find((i) => i.type === "browser-families" && i.id === familyId);
22
+ const browserName = `${family?.attributes?.name || "?"} ${browser?.attributes?.version || ""}`;
23
+ let output = `## Comparison #${args.comparison_id}\n\n`;
24
+ output += `| Field | Value |\n|---|---|\n`;
25
+ output += `| **Browser** | ${browserName} |\n`;
26
+ output += `| **Width** | ${attrs.width || "?"}px |\n`;
27
+ output += `| **State** | ${attrs.state || "?"} |\n`;
28
+ output += `| **Diff ratio** | ${attrs["diff-ratio"] != null ? (attrs["diff-ratio"] * 100).toFixed(2) + "%" : "—"} |\n`;
29
+ output += `| **AI diff ratio** | ${attrs["ai-diff-ratio"] != null ? (attrs["ai-diff-ratio"] * 100).toFixed(2) + "%" : "—"} |\n`;
30
+ output += `| **AI state** | ${attrs["ai-processing-state"] || "—"} |\n`;
31
+ output += `| **Potential bugs** | ${ai["total-potential-bugs"] ?? "—"} |\n`;
32
+ output += `| **AI visual diffs** | ${ai["total-ai-visual-diffs"] ?? "—"} |\n`;
33
+ output += `| **Diffs reduced** | ${ai["total-diffs-reduced-capped"] ?? "—"} |\n`;
34
+ // AI regions (the detailed change descriptions)
35
+ const regions = attrs["applied-regions"];
36
+ if (Array.isArray(regions) && regions.length > 0) {
37
+ output += `\n### AI Detected Changes (${regions.length})\n\n`;
38
+ regions.forEach((r, i) => {
39
+ const ignored = r.ignored ? " ~~ignored~~" : "";
40
+ output += `${i + 1}. **${r.change_title || r.change_type || "Change"}** (${r.change_type || "?"})${ignored}\n`;
41
+ if (r.change_description)
42
+ output += ` ${r.change_description}\n`;
43
+ if (r.change_reason)
44
+ output += ` *Reason: ${r.change_reason}*\n`;
45
+ if (r.coordinates) {
46
+ const c = r.coordinates;
47
+ output += ` Region: (${c.x || c.left || 0}, ${c.y || c.top || 0}) → (${c.x2 || c.right || c.x + c.width || 0}, ${c.y2 || c.bottom || c.y + c.height || 0})\n`;
48
+ }
49
+ output += "\n";
50
+ });
51
+ }
52
+ // Image URLs
53
+ const resolveImageUrl = (relName) => {
54
+ const screenshotId = comp.relationships?.[relName]?.data?.id;
55
+ if (!screenshotId)
56
+ return null;
57
+ // Direct image relationship
58
+ const directImage = included.find((i) => i.type === "images" && i.id === screenshotId);
59
+ if (directImage?.attributes?.url)
60
+ return directImage.attributes.url;
61
+ // Screenshot → image relationship
62
+ const screenshot = included.find((i) => i.type === "screenshots" && i.id === screenshotId);
63
+ const imageId = screenshot?.relationships?.image?.data?.id;
64
+ if (imageId) {
65
+ const image = included.find((i) => i.type === "images" && i.id === imageId);
66
+ return image?.attributes?.url || null;
67
+ }
68
+ return null;
69
+ };
70
+ output += `### Images\n\n`;
71
+ const headUrl = resolveImageUrl("head-screenshot");
72
+ const baseUrl = resolveImageUrl("base-screenshot");
73
+ const diffUrl = resolveImageUrl("diff-image");
74
+ const aiDiffUrl = resolveImageUrl("ai-diff-image");
75
+ if (headUrl)
76
+ output += `**Head:** ${headUrl}\n`;
77
+ if (baseUrl)
78
+ output += `**Base:** ${baseUrl}\n`;
79
+ if (diffUrl)
80
+ output += `**Diff:** ${diffUrl}\n`;
81
+ if (aiDiffUrl)
82
+ output += `**AI Diff:** ${aiDiffUrl}\n`;
83
+ if (!headUrl && !baseUrl && !diffUrl)
84
+ output += `No images available.\n`;
85
+ // Error info
86
+ if (attrs["error-buckets-exists"]) {
87
+ output += `\n### Errors\n\n`;
88
+ const assetFailures = attrs["asset-failure-category-counts"];
89
+ if (assetFailures) {
90
+ output += `**Asset failures:** ${JSON.stringify(assetFailures)}\n`;
91
+ }
92
+ }
93
+ return { content: [{ type: "text", text: output }] };
94
+ }
@@ -0,0 +1,5 @@
1
+ import { BrowserStackConfig } from "../../../lib/types.js";
2
+ import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
3
+ export declare function percyGetDevices(args: {
4
+ build_id?: string;
5
+ }, config: BrowserStackConfig): Promise<CallToolResult>;
@@ -0,0 +1,33 @@
1
+ import { percyGet } from "../../../lib/percy-api/percy-auth.js";
2
+ export async function percyGetDevices(args, config) {
3
+ // Get browser families
4
+ const families = await percyGet("/browser-families", config);
5
+ const familyList = families?.data || [];
6
+ let output = `## Percy Browsers & Devices\n\n`;
7
+ output += `### Browser Families\n\n`;
8
+ output += `| Name | Slug | ID |\n|---|---|---|\n`;
9
+ familyList.forEach((f) => {
10
+ output += `| ${f.attributes?.name || "?"} | ${f.attributes?.slug || "?"} | ${f.id} |\n`;
11
+ });
12
+ // Get device details if build_id provided
13
+ if (args.build_id) {
14
+ try {
15
+ const devices = await percyGet("/discovery/device-details", config, {
16
+ build_id: args.build_id,
17
+ });
18
+ const deviceList = devices?.data || devices || [];
19
+ if (Array.isArray(deviceList) && deviceList.length) {
20
+ output += `\n### Devices for Build ${args.build_id}\n\n`;
21
+ output += `| Device | Width | Height |\n|---|---|---|\n`;
22
+ deviceList.forEach((d) => {
23
+ const attrs = d.attributes || d;
24
+ output += `| ${attrs.name || "?"} | ${attrs.width || "?"} | ${attrs.height || "?"} |\n`;
25
+ });
26
+ }
27
+ }
28
+ catch {
29
+ /* device details may not be available */
30
+ }
31
+ }
32
+ return { content: [{ type: "text", text: output }] };
33
+ }
@@ -0,0 +1,7 @@
1
+ import { BrowserStackConfig } from "../../../lib/types.js";
2
+ import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
3
+ export declare function percyGetInsights(args: {
4
+ org_slug: string;
5
+ period?: string;
6
+ product?: string;
7
+ }, config: BrowserStackConfig): Promise<CallToolResult>;
@@ -0,0 +1,52 @@
1
+ import { percyGet } from "../../../lib/percy-api/percy-auth.js";
2
+ export async function percyGetInsights(args, config) {
3
+ const params = {
4
+ organization_id: args.org_slug,
5
+ period: args.period || "last_30_days",
6
+ product: args.product || "web",
7
+ };
8
+ const response = await percyGet("/insights/metrics", config, params);
9
+ const data = response?.data?.attributes || response?.data || {};
10
+ let output = `## Percy Testing Insights — ${args.org_slug}\n\n`;
11
+ output += `**Period:** ${params.period}\n**Product:** ${params.product}\n\n`;
12
+ // Review efficiency
13
+ const review = data.reviewEfficiency || data["review-efficiency"] || {};
14
+ if (review) {
15
+ output += `### Review Efficiency\n`;
16
+ output += `| Metric | Value |\n|---|---|\n`;
17
+ if (review.meaningfulReviewTimeRatio != null)
18
+ output += `| Meaningful review ratio | ${(review.meaningfulReviewTimeRatio * 100).toFixed(0)}% |\n`;
19
+ if (review.totalReviews != null)
20
+ output += `| Total reviews | ${review.totalReviews} |\n`;
21
+ if (review.noisyReviews != null)
22
+ output += `| Noisy reviews | ${review.noisyReviews} |\n`;
23
+ if (review.medianReviewTimeSeconds != null)
24
+ output += `| Median review time | ${review.medianReviewTimeSeconds}s |\n`;
25
+ output += "\n";
26
+ }
27
+ // ROI
28
+ const roi = data.roiTimeSavings || data["roi-time-savings"] || {};
29
+ if (roi) {
30
+ output += `### ROI & Time Savings\n`;
31
+ output += `| Metric | Value |\n|---|---|\n`;
32
+ if (roi.totalTimeSaved != null)
33
+ output += `| Total time saved | ${roi.totalTimeSaved} min |\n`;
34
+ if (roi.noDiffPercentage != null)
35
+ output += `| No-diff percentage | ${(roi.noDiffPercentage * 100).toFixed(0)}% |\n`;
36
+ if (roi.buildsCount != null)
37
+ output += `| Builds | ${roi.buildsCount} |\n`;
38
+ output += "\n";
39
+ }
40
+ // Coverage
41
+ const coverage = data.coverage || {};
42
+ if (coverage) {
43
+ output += `### Coverage\n`;
44
+ output += `| Metric | Value |\n|---|---|\n`;
45
+ if (coverage.coveragePercentage != null)
46
+ output += `| Coverage | ${coverage.coveragePercentage.toFixed(0)}% |\n`;
47
+ if (coverage.activeSnapshotsCount != null)
48
+ output += `| Active snapshots | ${coverage.activeSnapshotsCount} |\n`;
49
+ output += "\n";
50
+ }
51
+ return { content: [{ type: "text", text: output }] };
52
+ }
@@ -0,0 +1,6 @@
1
+ import { BrowserStackConfig } from "../../../lib/types.js";
2
+ import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
3
+ export declare function percyGetProjectsV2(args: {
4
+ search?: string;
5
+ limit?: number;
6
+ }, config: BrowserStackConfig): Promise<CallToolResult>;
@@ -0,0 +1,41 @@
1
+ import { percyGet } from "../../../lib/percy-api/percy-auth.js";
2
+ import { setOrg } from "../../../lib/percy-api/percy-session.js";
3
+ export async function percyGetProjectsV2(args, config) {
4
+ const params = {};
5
+ if (args.search)
6
+ params["filter[search]"] = args.search;
7
+ params["page[limit]"] = String(args.limit || 20);
8
+ const response = await percyGet("/projects", config, params);
9
+ const projects = response?.data || [];
10
+ if (projects.length === 0) {
11
+ return {
12
+ content: [
13
+ {
14
+ type: "text",
15
+ text: "No projects found. Use `percy_create_project` to create one.",
16
+ },
17
+ ],
18
+ };
19
+ }
20
+ // Extract org slug from the first project's full-slug
21
+ const firstSlug = projects[0]?.attributes?.["full-slug"] || "";
22
+ const orgSlug = firstSlug.split("/")[0] || "";
23
+ if (orgSlug)
24
+ setOrg({ slug: orgSlug });
25
+ let output = `## Percy Projects (${projects.length})\n\n`;
26
+ output += `| # | Name | ID | Type | Slug (for builds) |\n|---|---|---|---|---|\n`;
27
+ projects.forEach((p, i) => {
28
+ const name = p.attributes?.name || "?";
29
+ const type = p.attributes?.type || "?";
30
+ const fullSlug = p.attributes?.["full-slug"] || p.attributes?.slug || "?";
31
+ output += `| ${i + 1} | ${name} | ${p.id} | ${type} | \`${fullSlug}\` |\n`;
32
+ });
33
+ if (orgSlug) {
34
+ output += `\n**Organization:** ${orgSlug}\n`;
35
+ }
36
+ output += `\n### Usage\n\n`;
37
+ output += `- \`percy_get_builds\` with project_slug "${firstSlug}" — List builds for a project\n`;
38
+ output += `- \`percy_create_project\` with name "my-project" — Create new project & activate token\n`;
39
+ output += `- \`percy_create_build\` with project_name "my-project" — Create a build\n`;
40
+ return { content: [{ type: "text", text: output }] };
41
+ }
@@ -0,0 +1,5 @@
1
+ import { BrowserStackConfig } from "../../../lib/types.js";
2
+ import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
3
+ export declare function percyGetSnapshot(args: {
4
+ snapshot_id: string;
5
+ }, config: BrowserStackConfig): Promise<CallToolResult>;
@@ -0,0 +1,96 @@
1
+ import { percyGet } from "../../../lib/percy-api/percy-auth.js";
2
+ export async function percyGetSnapshot(args, config) {
3
+ const response = await percyGet(`/snapshots/${args.snapshot_id}`, config, {
4
+ include: [
5
+ "comparisons.head-screenshot.image",
6
+ "comparisons.base-screenshot.image",
7
+ "comparisons.diff-image",
8
+ "comparisons.ai-diff-image",
9
+ "comparisons.browser.browser-family",
10
+ "comparisons.comparison-tag",
11
+ ].join(","),
12
+ });
13
+ const snap = response?.data || {};
14
+ const attrs = snap.attributes || {};
15
+ const included = response?.included || [];
16
+ let output = `## Snapshot: ${attrs.name || args.snapshot_id}\n\n`;
17
+ if (attrs["display-name"] && attrs["display-name"] !== attrs.name) {
18
+ output += `**Display name:** ${attrs["display-name"]}\n`;
19
+ }
20
+ output += `| Field | Value |\n|---|---|\n`;
21
+ output += `| **Review** | ${attrs["review-state"] || "—"} (${attrs["review-state-reason"] || "—"}) |\n`;
22
+ output += `| **Diff ratio** | ${attrs["diff-ratio"] != null ? (attrs["diff-ratio"] * 100).toFixed(2) + "%" : "—"} |\n`;
23
+ output += `| **Test case** | ${attrs["test-case-name"] || "none"} |\n`;
24
+ output += `| **Comments** | ${attrs["total-open-comments"] ?? 0} |\n`;
25
+ output += `| **Layout** | ${attrs["enable-layout"] ? "enabled" : "disabled"} |\n`;
26
+ // Comparisons table
27
+ const comps = included.filter((i) => i.type === "comparisons");
28
+ const browsers = new Map(included
29
+ .filter((i) => i.type === "browsers")
30
+ .map((b) => {
31
+ const family = included.find((f) => f.type === "browser-families" &&
32
+ f.id === b.relationships?.["browser-family"]?.data?.id);
33
+ return [
34
+ b.id,
35
+ `${family?.attributes?.name || "?"} ${b.attributes?.version || ""}`,
36
+ ];
37
+ }));
38
+ const images = new Map(included
39
+ .filter((i) => i.type === "images")
40
+ .map((img) => [img.id, img.attributes]));
41
+ if (comps.length > 0) {
42
+ output += `\n### Comparisons (${comps.length})\n\n`;
43
+ output += `| Browser | Width | Diff | AI Diff | AI State | Bugs |\n|---|---|---|---|---|---|\n`;
44
+ comps.forEach((c) => {
45
+ const ca = c.attributes || {};
46
+ const browserId = c.relationships?.browser?.data?.id;
47
+ const browserName = browsers.get(browserId) || "?";
48
+ const diff = ca["diff-ratio"] != null
49
+ ? (ca["diff-ratio"] * 100).toFixed(1) + "%"
50
+ : "—";
51
+ const aiDiff = ca["ai-diff-ratio"] != null
52
+ ? (ca["ai-diff-ratio"] * 100).toFixed(1) + "%"
53
+ : "—";
54
+ const aiState = ca["ai-processing-state"] || "—";
55
+ const bugs = ca["ai-details"]?.["total-potential-bugs"] ?? "—";
56
+ output += `| ${browserName} | ${ca.width || "?"}px | ${diff} | ${aiDiff} | ${aiState} | ${bugs} |\n`;
57
+ });
58
+ // Show AI regions for comparisons that have them
59
+ const compsWithRegions = comps.filter((c) => c.attributes?.["applied-regions"]?.length > 0);
60
+ if (compsWithRegions.length > 0) {
61
+ output += `\n### AI Detected Changes\n\n`;
62
+ for (const c of compsWithRegions) {
63
+ const regions = c.attributes["applied-regions"];
64
+ regions.forEach((r) => {
65
+ const ignored = r.ignored ? " *(ignored)*" : "";
66
+ output += `- **${r.change_title || r.change_type || "Change"}** (${r.change_type || "?"})${ignored}\n`;
67
+ if (r.change_description)
68
+ output += ` ${r.change_description}\n`;
69
+ if (r.change_reason)
70
+ output += ` *Reason: ${r.change_reason}*\n`;
71
+ });
72
+ }
73
+ }
74
+ // Image URLs for first comparison
75
+ const firstComp = comps[0];
76
+ const headScreenshotId = firstComp?.relationships?.["head-screenshot"]?.data?.id;
77
+ const headScreenshot = included.find((i) => i.type === "screenshots" && i.id === headScreenshotId);
78
+ const headImageId = headScreenshot?.relationships?.image?.data?.id;
79
+ const headImage = headImageId ? images.get(headImageId) : null;
80
+ if (headImage?.url) {
81
+ output += `\n### Images (first comparison)\n\n`;
82
+ output += `**Head:** ${headImage.url}\n`;
83
+ const baseScreenshotId = firstComp?.relationships?.["base-screenshot"]?.data?.id;
84
+ const baseScreenshot = included.find((i) => i.type === "screenshots" && i.id === baseScreenshotId);
85
+ const baseImageId = baseScreenshot?.relationships?.image?.data?.id;
86
+ const baseImage = baseImageId ? images.get(baseImageId) : null;
87
+ if (baseImage?.url)
88
+ output += `**Base:** ${baseImage.url}\n`;
89
+ const diffImageId = firstComp?.relationships?.["diff-image"]?.data?.id;
90
+ const diffImage = diffImageId ? images.get(diffImageId) : null;
91
+ if (diffImage?.url)
92
+ output += `**Diff:** ${diffImage.url}\n`;
93
+ }
94
+ }
95
+ return { content: [{ type: "text", text: output }] };
96
+ }
@@ -0,0 +1,5 @@
1
+ import { BrowserStackConfig } from "../../../lib/types.js";
2
+ import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
3
+ export declare function percyGetTestCaseHistory(args: {
4
+ test_case_id: string;
5
+ }, config: BrowserStackConfig): Promise<CallToolResult>;
@@ -0,0 +1,20 @@
1
+ import { percyGet } from "../../../lib/percy-api/percy-auth.js";
2
+ export async function percyGetTestCaseHistory(args, config) {
3
+ const response = await percyGet("/test-case-histories", config, {
4
+ test_case_id: args.test_case_id,
5
+ });
6
+ const history = response?.data || [];
7
+ if (!history.length) {
8
+ return {
9
+ content: [{ type: "text", text: "No history found for this test case." }],
10
+ };
11
+ }
12
+ let output = `## Test Case History\n\n`;
13
+ output += `| # | Build | State | Total | Failed | Unreviewed |\n|---|---|---|---|---|---|\n`;
14
+ history.forEach((entry, i) => {
15
+ const attrs = entry.attributes || {};
16
+ const buildId = entry.relationships?.build?.data?.id || "?";
17
+ output += `| ${i + 1} | #${buildId} | ${attrs["review-state"] ?? "?"} | ${attrs["total-snapshots"] ?? "?"} | ${attrs["failed-snapshots"] ?? "?"} | ${attrs["unreviewed-snapshots"] ?? "?"} |\n`;
18
+ });
19
+ return { content: [{ type: "text", text: output }] };
20
+ }
@@ -0,0 +1,6 @@
1
+ import { BrowserStackConfig } from "../../../lib/types.js";
2
+ import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
3
+ export declare function percyGetTestCases(args: {
4
+ project_id: string;
5
+ build_id?: string;
6
+ }, config: BrowserStackConfig): Promise<CallToolResult>;
@@ -0,0 +1,36 @@
1
+ import { percyGet } from "../../../lib/percy-api/percy-auth.js";
2
+ export async function percyGetTestCases(args, config) {
3
+ // Get test cases
4
+ const params = { project_id: args.project_id };
5
+ const response = await percyGet("/test-cases", config, params);
6
+ const testCases = response?.data || [];
7
+ if (!testCases.length) {
8
+ return {
9
+ content: [
10
+ { type: "text", text: "No test cases found for this project." },
11
+ ],
12
+ };
13
+ }
14
+ let output = `## Test Cases (${testCases.length})\n\n`;
15
+ output += `| # | Name | ID |\n|---|---|---|\n`;
16
+ testCases.forEach((tc, i) => {
17
+ const name = tc.attributes?.name || tc.name || "?";
18
+ output += `| ${i + 1} | ${name} | ${tc.id} |\n`;
19
+ });
20
+ // If build_id provided, get executions
21
+ if (args.build_id) {
22
+ const execResponse = await percyGet("/test-case-executions", config, {
23
+ build_id: args.build_id,
24
+ });
25
+ const executions = execResponse?.data || [];
26
+ if (executions.length) {
27
+ output += `\n### Executions for Build ${args.build_id}\n\n`;
28
+ output += `| Test Case | Total | Failed | Unreviewed | State |\n|---|---|---|---|---|\n`;
29
+ executions.forEach((exec) => {
30
+ const attrs = exec.attributes || {};
31
+ output += `| ${attrs["test-case-name"] || exec.id} | ${attrs["total-snapshots"] ?? "?"} | ${attrs["failed-snapshots"] ?? "?"} | ${attrs["unreviewed-snapshots"] ?? "?"} | ${attrs["review-state"] ?? "?"} |\n`;
32
+ });
33
+ }
34
+ }
35
+ return { content: [{ type: "text", text: output }] };
36
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Percy MCP Tools v2 — Simplified, production-ready tools.
3
+ *
4
+ * Key changes from v1:
5
+ * - ALL read operations use BrowserStack Basic Auth (not Percy Token)
6
+ * - Fewer, more powerful tools (quality > quantity)
7
+ * - Every tool tested against real Percy API
8
+ *
9
+ * Tools (21 total):
10
+ * percy_create_project — Create/get a Percy project
11
+ * percy_create_build — Create build (URL snapshot / screenshot upload / test wrap)
12
+ * percy_get_projects — List projects
13
+ * percy_get_builds — List builds with filters
14
+ * percy_auth_status — Check auth
15
+ * percy_figma_build — Create build from Figma designs
16
+ * percy_figma_baseline — Update Figma design baseline
17
+ * percy_figma_link — Get Figma link for snapshot/comparison
18
+ * percy_get_insights — Testing health metrics
19
+ * percy_manage_insights_email — Configure insights email recipients
20
+ * percy_get_test_cases — List test cases for a project
21
+ * percy_get_test_case_history — Test case execution history
22
+ * percy_discover_urls — Discover URLs from sitemaps
23
+ * percy_get_devices — List browsers/devices/viewports
24
+ * percy_manage_domains — Get/update allowed/error domains
25
+ * percy_manage_usage_alerts — Configure usage alert thresholds
26
+ * percy_preview_comparison — Trigger on-demand diff recomputation
27
+ * percy_search_builds — Advanced build item search
28
+ * percy_list_integrations — List org integrations
29
+ * percy_migrate_integrations — Migrate integrations between orgs
30
+ * percy_create_app_build — Create App Percy BYOS build from device screenshots
31
+ */
32
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
33
+ import { BrowserStackConfig } from "../../../lib/types.js";
34
+ export declare function registerPercyMcpToolsV2(server: McpServer, config: BrowserStackConfig): Record<string, any>;
35
+ export default registerPercyMcpToolsV2;