@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.
- package/dist/lib/percy-api/auth.d.ts +41 -0
- package/dist/lib/percy-api/auth.js +96 -0
- package/dist/lib/percy-api/cache.d.ts +28 -0
- package/dist/lib/percy-api/cache.js +48 -0
- package/dist/lib/percy-api/client.d.ts +69 -0
- package/dist/lib/percy-api/client.js +275 -0
- package/dist/lib/percy-api/errors.d.ts +15 -0
- package/dist/lib/percy-api/errors.js +52 -0
- package/dist/lib/percy-api/formatter.d.ts +16 -0
- package/dist/lib/percy-api/formatter.js +344 -0
- package/dist/lib/percy-api/percy-auth.d.ts +43 -0
- package/dist/lib/percy-api/percy-auth.js +137 -0
- package/dist/lib/percy-api/percy-error-handler.d.ts +24 -0
- package/dist/lib/percy-api/percy-error-handler.js +302 -0
- package/dist/lib/percy-api/percy-session.d.ts +42 -0
- package/dist/lib/percy-api/percy-session.js +87 -0
- package/dist/lib/percy-api/polling.d.ts +26 -0
- package/dist/lib/percy-api/polling.js +42 -0
- package/dist/lib/percy-api/types.d.ts +56 -0
- package/dist/lib/percy-api/types.js +76 -0
- package/dist/server-factory.js +4 -0
- package/dist/tools/percy-mcp/advanced/branchline-operations.d.ts +16 -0
- package/dist/tools/percy-mcp/advanced/branchline-operations.js +81 -0
- package/dist/tools/percy-mcp/advanced/manage-variants.d.ts +16 -0
- package/dist/tools/percy-mcp/advanced/manage-variants.js +155 -0
- package/dist/tools/percy-mcp/advanced/manage-visual-monitoring.d.ts +16 -0
- package/dist/tools/percy-mcp/advanced/manage-visual-monitoring.js +171 -0
- package/dist/tools/percy-mcp/auth/auth-status.d.ts +3 -0
- package/dist/tools/percy-mcp/auth/auth-status.js +131 -0
- package/dist/tools/percy-mcp/core/approve-build.d.ts +14 -0
- package/dist/tools/percy-mcp/core/approve-build.js +97 -0
- package/dist/tools/percy-mcp/core/get-build-items.d.ts +13 -0
- package/dist/tools/percy-mcp/core/get-build-items.js +65 -0
- package/dist/tools/percy-mcp/core/get-build.d.ts +10 -0
- package/dist/tools/percy-mcp/core/get-build.js +16 -0
- package/dist/tools/percy-mcp/core/get-comparison.d.ts +11 -0
- package/dist/tools/percy-mcp/core/get-comparison.js +59 -0
- package/dist/tools/percy-mcp/core/get-snapshot.d.ts +10 -0
- package/dist/tools/percy-mcp/core/get-snapshot.js +40 -0
- package/dist/tools/percy-mcp/core/list-builds.d.ts +14 -0
- package/dist/tools/percy-mcp/core/list-builds.js +45 -0
- package/dist/tools/percy-mcp/core/list-projects.d.ts +12 -0
- package/dist/tools/percy-mcp/core/list-projects.js +51 -0
- package/dist/tools/percy-mcp/creation/create-app-snapshot.d.ts +12 -0
- package/dist/tools/percy-mcp/creation/create-app-snapshot.js +29 -0
- package/dist/tools/percy-mcp/creation/create-build.d.ts +19 -0
- package/dist/tools/percy-mcp/creation/create-build.js +68 -0
- package/dist/tools/percy-mcp/creation/create-comparison.d.ts +18 -0
- package/dist/tools/percy-mcp/creation/create-comparison.js +90 -0
- package/dist/tools/percy-mcp/creation/create-snapshot.d.ts +17 -0
- package/dist/tools/percy-mcp/creation/create-snapshot.js +99 -0
- package/dist/tools/percy-mcp/creation/finalize-build.d.ts +12 -0
- package/dist/tools/percy-mcp/creation/finalize-build.js +33 -0
- package/dist/tools/percy-mcp/creation/finalize-comparison.d.ts +10 -0
- package/dist/tools/percy-mcp/creation/finalize-comparison.js +16 -0
- package/dist/tools/percy-mcp/creation/finalize-snapshot.d.ts +12 -0
- package/dist/tools/percy-mcp/creation/finalize-snapshot.js +33 -0
- package/dist/tools/percy-mcp/creation/upload-resource.d.ts +15 -0
- package/dist/tools/percy-mcp/creation/upload-resource.js +43 -0
- package/dist/tools/percy-mcp/creation/upload-tile.d.ts +11 -0
- package/dist/tools/percy-mcp/creation/upload-tile.js +53 -0
- package/dist/tools/percy-mcp/diagnostics/analyze-logs-realtime.d.ts +13 -0
- package/dist/tools/percy-mcp/diagnostics/analyze-logs-realtime.js +65 -0
- package/dist/tools/percy-mcp/diagnostics/get-build-logs.d.ts +17 -0
- package/dist/tools/percy-mcp/diagnostics/get-build-logs.js +74 -0
- package/dist/tools/percy-mcp/diagnostics/get-network-logs.d.ts +5 -0
- package/dist/tools/percy-mcp/diagnostics/get-network-logs.js +21 -0
- package/dist/tools/percy-mcp/diagnostics/get-suggestions.d.ts +7 -0
- package/dist/tools/percy-mcp/diagnostics/get-suggestions.js +24 -0
- package/dist/tools/percy-mcp/index.d.ts +36 -0
- package/dist/tools/percy-mcp/index.js +1137 -0
- package/dist/tools/percy-mcp/intelligence/get-ai-analysis.d.ts +15 -0
- package/dist/tools/percy-mcp/intelligence/get-ai-analysis.js +166 -0
- package/dist/tools/percy-mcp/intelligence/get-ai-quota.d.ts +9 -0
- package/dist/tools/percy-mcp/intelligence/get-ai-quota.js +73 -0
- package/dist/tools/percy-mcp/intelligence/get-build-summary.d.ts +11 -0
- package/dist/tools/percy-mcp/intelligence/get-build-summary.js +78 -0
- package/dist/tools/percy-mcp/intelligence/get-rca.d.ts +6 -0
- package/dist/tools/percy-mcp/intelligence/get-rca.js +153 -0
- package/dist/tools/percy-mcp/intelligence/suggest-prompt.d.ts +15 -0
- package/dist/tools/percy-mcp/intelligence/suggest-prompt.js +86 -0
- package/dist/tools/percy-mcp/intelligence/trigger-ai-recompute.d.ts +16 -0
- package/dist/tools/percy-mcp/intelligence/trigger-ai-recompute.js +64 -0
- package/dist/tools/percy-mcp/management/create-project.d.ts +14 -0
- package/dist/tools/percy-mcp/management/create-project.js +52 -0
- package/dist/tools/percy-mcp/management/get-usage-stats.d.ts +12 -0
- package/dist/tools/percy-mcp/management/get-usage-stats.js +61 -0
- package/dist/tools/percy-mcp/management/manage-browser-targets.d.ts +12 -0
- package/dist/tools/percy-mcp/management/manage-browser-targets.js +136 -0
- package/dist/tools/percy-mcp/management/manage-comments.d.ts +14 -0
- package/dist/tools/percy-mcp/management/manage-comments.js +147 -0
- package/dist/tools/percy-mcp/management/manage-ignored-regions.d.ts +18 -0
- package/dist/tools/percy-mcp/management/manage-ignored-regions.js +182 -0
- package/dist/tools/percy-mcp/management/manage-project-settings.d.ts +16 -0
- package/dist/tools/percy-mcp/management/manage-project-settings.js +97 -0
- package/dist/tools/percy-mcp/management/manage-tokens.d.ts +14 -0
- package/dist/tools/percy-mcp/management/manage-tokens.js +90 -0
- package/dist/tools/percy-mcp/management/manage-webhooks.d.ts +15 -0
- package/dist/tools/percy-mcp/management/manage-webhooks.js +180 -0
- package/dist/tools/percy-mcp/v2/auth-status.d.ts +3 -0
- package/dist/tools/percy-mcp/v2/auth-status.js +80 -0
- package/dist/tools/percy-mcp/v2/clone-build.d.ts +24 -0
- package/dist/tools/percy-mcp/v2/clone-build.js +539 -0
- package/dist/tools/percy-mcp/v2/create-app-build.d.ts +28 -0
- package/dist/tools/percy-mcp/v2/create-app-build.js +442 -0
- package/dist/tools/percy-mcp/v2/create-build.d.ts +16 -0
- package/dist/tools/percy-mcp/v2/create-build.js +601 -0
- package/dist/tools/percy-mcp/v2/create-project.d.ts +8 -0
- package/dist/tools/percy-mcp/v2/create-project.js +33 -0
- package/dist/tools/percy-mcp/v2/discover-urls.d.ts +7 -0
- package/dist/tools/percy-mcp/v2/discover-urls.js +38 -0
- package/dist/tools/percy-mcp/v2/figma-baseline.d.ts +7 -0
- package/dist/tools/percy-mcp/v2/figma-baseline.js +18 -0
- package/dist/tools/percy-mcp/v2/figma-build.d.ts +7 -0
- package/dist/tools/percy-mcp/v2/figma-build.js +39 -0
- package/dist/tools/percy-mcp/v2/figma-link.d.ts +6 -0
- package/dist/tools/percy-mcp/v2/figma-link.js +27 -0
- package/dist/tools/percy-mcp/v2/get-ai-summary.d.ts +5 -0
- package/dist/tools/percy-mcp/v2/get-ai-summary.js +109 -0
- package/dist/tools/percy-mcp/v2/get-build-detail.d.ts +22 -0
- package/dist/tools/percy-mcp/v2/get-build-detail.js +567 -0
- package/dist/tools/percy-mcp/v2/get-builds.d.ts +8 -0
- package/dist/tools/percy-mcp/v2/get-builds.js +63 -0
- package/dist/tools/percy-mcp/v2/get-comparison.d.ts +5 -0
- package/dist/tools/percy-mcp/v2/get-comparison.js +94 -0
- package/dist/tools/percy-mcp/v2/get-devices.d.ts +5 -0
- package/dist/tools/percy-mcp/v2/get-devices.js +33 -0
- package/dist/tools/percy-mcp/v2/get-insights.d.ts +7 -0
- package/dist/tools/percy-mcp/v2/get-insights.js +52 -0
- package/dist/tools/percy-mcp/v2/get-projects.d.ts +6 -0
- package/dist/tools/percy-mcp/v2/get-projects.js +41 -0
- package/dist/tools/percy-mcp/v2/get-snapshot.d.ts +5 -0
- package/dist/tools/percy-mcp/v2/get-snapshot.js +96 -0
- package/dist/tools/percy-mcp/v2/get-test-case-history.d.ts +5 -0
- package/dist/tools/percy-mcp/v2/get-test-case-history.js +20 -0
- package/dist/tools/percy-mcp/v2/get-test-cases.d.ts +6 -0
- package/dist/tools/percy-mcp/v2/get-test-cases.js +36 -0
- package/dist/tools/percy-mcp/v2/index.d.ts +35 -0
- package/dist/tools/percy-mcp/v2/index.js +544 -0
- package/dist/tools/percy-mcp/v2/list-integrations.d.ts +5 -0
- package/dist/tools/percy-mcp/v2/list-integrations.js +41 -0
- package/dist/tools/percy-mcp/v2/manage-domains.d.ts +8 -0
- package/dist/tools/percy-mcp/v2/manage-domains.js +33 -0
- package/dist/tools/percy-mcp/v2/manage-insights-email.d.ts +8 -0
- package/dist/tools/percy-mcp/v2/manage-insights-email.js +49 -0
- package/dist/tools/percy-mcp/v2/manage-usage-alerts.d.ts +10 -0
- package/dist/tools/percy-mcp/v2/manage-usage-alerts.js +43 -0
- package/dist/tools/percy-mcp/v2/migrate-integrations.d.ts +6 -0
- package/dist/tools/percy-mcp/v2/migrate-integrations.js +20 -0
- package/dist/tools/percy-mcp/v2/preview-comparison.d.ts +5 -0
- package/dist/tools/percy-mcp/v2/preview-comparison.js +17 -0
- package/dist/tools/percy-mcp/v2/search-build-items.d.ts +12 -0
- package/dist/tools/percy-mcp/v2/search-build-items.js +45 -0
- package/dist/tools/percy-mcp/workflows/auto-triage.d.ts +7 -0
- package/dist/tools/percy-mcp/workflows/auto-triage.js +82 -0
- package/dist/tools/percy-mcp/workflows/clone-build.d.ts +22 -0
- package/dist/tools/percy-mcp/workflows/clone-build.js +414 -0
- package/dist/tools/percy-mcp/workflows/create-percy-build.d.ts +32 -0
- package/dist/tools/percy-mcp/workflows/create-percy-build.js +434 -0
- package/dist/tools/percy-mcp/workflows/debug-failed-build.d.ts +5 -0
- package/dist/tools/percy-mcp/workflows/debug-failed-build.js +122 -0
- package/dist/tools/percy-mcp/workflows/diff-explain.d.ts +6 -0
- package/dist/tools/percy-mcp/workflows/diff-explain.js +147 -0
- package/dist/tools/percy-mcp/workflows/pr-visual-report.d.ts +8 -0
- package/dist/tools/percy-mcp/workflows/pr-visual-report.js +184 -0
- package/dist/tools/percy-mcp/workflows/run-tests.d.ts +17 -0
- package/dist/tools/percy-mcp/workflows/run-tests.js +107 -0
- package/dist/tools/percy-mcp/workflows/snapshot-urls.d.ts +18 -0
- package/dist/tools/percy-mcp/workflows/snapshot-urls.js +197 -0
- package/package.json +4 -3
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* percy_branchline_operations — Sync, merge, or unmerge Percy branch baselines.
|
|
3
|
+
*
|
|
4
|
+
* Sync copies approved baselines to target branches.
|
|
5
|
+
*/
|
|
6
|
+
import { PercyClient } from "../../../lib/percy-api/client.js";
|
|
7
|
+
export async function percyBranchlineOperations(args, config) {
|
|
8
|
+
const { action, project_id, build_id, target_branch_filter, snapshot_ids } = args;
|
|
9
|
+
const client = new PercyClient(config);
|
|
10
|
+
const VALID_ACTIONS = ["sync", "merge", "unmerge"];
|
|
11
|
+
if (!VALID_ACTIONS.includes(action)) {
|
|
12
|
+
return {
|
|
13
|
+
content: [
|
|
14
|
+
{
|
|
15
|
+
type: "text",
|
|
16
|
+
text: `Invalid action "${action}". Valid actions: ${VALID_ACTIONS.join(", ")}`,
|
|
17
|
+
},
|
|
18
|
+
],
|
|
19
|
+
isError: true,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
// Build the request body based on action
|
|
23
|
+
const attrs = {};
|
|
24
|
+
const relationships = {};
|
|
25
|
+
if (project_id) {
|
|
26
|
+
relationships.project = {
|
|
27
|
+
data: { type: "projects", id: project_id },
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
if (build_id) {
|
|
31
|
+
relationships.build = {
|
|
32
|
+
data: { type: "builds", id: build_id },
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
if (target_branch_filter) {
|
|
36
|
+
attrs["target-branch-filter"] = target_branch_filter;
|
|
37
|
+
}
|
|
38
|
+
if (snapshot_ids) {
|
|
39
|
+
relationships.snapshots = {
|
|
40
|
+
data: snapshot_ids
|
|
41
|
+
.split(",")
|
|
42
|
+
.map((id) => id.trim())
|
|
43
|
+
.filter(Boolean)
|
|
44
|
+
.map((id) => ({ type: "snapshots", id })),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
const body = {
|
|
48
|
+
data: {
|
|
49
|
+
type: "branchline",
|
|
50
|
+
attributes: attrs,
|
|
51
|
+
...(Object.keys(relationships).length > 0 ? { relationships } : {}),
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
try {
|
|
55
|
+
await client.post(`/branchline/${action}`, body);
|
|
56
|
+
const lines = [];
|
|
57
|
+
lines.push(`## Branchline ${action.charAt(0).toUpperCase() + action.slice(1)} Complete`);
|
|
58
|
+
lines.push("");
|
|
59
|
+
if (build_id)
|
|
60
|
+
lines.push(`**Build:** ${build_id}`);
|
|
61
|
+
if (project_id)
|
|
62
|
+
lines.push(`**Project:** ${project_id}`);
|
|
63
|
+
if (target_branch_filter)
|
|
64
|
+
lines.push(`**Target Branch Filter:** ${target_branch_filter}`);
|
|
65
|
+
if (snapshot_ids)
|
|
66
|
+
lines.push(`**Snapshot IDs:** ${snapshot_ids}`);
|
|
67
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
71
|
+
return {
|
|
72
|
+
content: [
|
|
73
|
+
{
|
|
74
|
+
type: "text",
|
|
75
|
+
text: `Failed to ${action} branchline: ${message}`,
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
isError: true,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* percy_manage_variants — List, create, or update A/B testing variants
|
|
3
|
+
* for Percy snapshot comparisons.
|
|
4
|
+
*/
|
|
5
|
+
import { BrowserStackConfig } from "../../../lib/types.js";
|
|
6
|
+
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
7
|
+
interface ManageVariantsArgs {
|
|
8
|
+
comparison_id?: string;
|
|
9
|
+
snapshot_id?: string;
|
|
10
|
+
action?: string;
|
|
11
|
+
variant_id?: string;
|
|
12
|
+
name?: string;
|
|
13
|
+
state?: string;
|
|
14
|
+
}
|
|
15
|
+
export declare function percyManageVariants(args: ManageVariantsArgs, config: BrowserStackConfig): Promise<CallToolResult>;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* percy_manage_variants — List, create, or update A/B testing variants
|
|
3
|
+
* for Percy snapshot comparisons.
|
|
4
|
+
*/
|
|
5
|
+
import { PercyClient } from "../../../lib/percy-api/client.js";
|
|
6
|
+
export async function percyManageVariants(args, config) {
|
|
7
|
+
const { comparison_id, snapshot_id, action = "list", variant_id, name, state, } = args;
|
|
8
|
+
const client = new PercyClient(config);
|
|
9
|
+
// ---- List ----
|
|
10
|
+
if (action === "list") {
|
|
11
|
+
if (!comparison_id) {
|
|
12
|
+
return {
|
|
13
|
+
content: [
|
|
14
|
+
{
|
|
15
|
+
type: "text",
|
|
16
|
+
text: "comparison_id is required for the 'list' action.",
|
|
17
|
+
},
|
|
18
|
+
],
|
|
19
|
+
isError: true,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
const response = await client.get("/variants", { comparison_id });
|
|
23
|
+
const variants = Array.isArray(response?.data) ? response.data : [];
|
|
24
|
+
if (variants.length === 0) {
|
|
25
|
+
return {
|
|
26
|
+
content: [
|
|
27
|
+
{ type: "text", text: "_No variants found for this comparison._" },
|
|
28
|
+
],
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
const lines = [];
|
|
32
|
+
lines.push(`## Variants (Comparison: ${comparison_id})`);
|
|
33
|
+
lines.push("");
|
|
34
|
+
lines.push("| ID | Name | State |");
|
|
35
|
+
lines.push("|----|------|-------|");
|
|
36
|
+
for (const variant of variants) {
|
|
37
|
+
const attrs = variant.attributes ?? variant;
|
|
38
|
+
const vName = attrs.name ?? "Unnamed";
|
|
39
|
+
const vState = attrs.state ?? "—";
|
|
40
|
+
lines.push(`| ${variant.id ?? "?"} | ${vName} | ${vState} |`);
|
|
41
|
+
}
|
|
42
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
43
|
+
}
|
|
44
|
+
// ---- Create ----
|
|
45
|
+
if (action === "create") {
|
|
46
|
+
if (!snapshot_id) {
|
|
47
|
+
return {
|
|
48
|
+
content: [
|
|
49
|
+
{
|
|
50
|
+
type: "text",
|
|
51
|
+
text: "snapshot_id is required for the 'create' action.",
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
isError: true,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
if (!name) {
|
|
58
|
+
return {
|
|
59
|
+
content: [
|
|
60
|
+
{ type: "text", text: "name is required for the 'create' action." },
|
|
61
|
+
],
|
|
62
|
+
isError: true,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
const body = {
|
|
66
|
+
data: {
|
|
67
|
+
type: "variants",
|
|
68
|
+
attributes: {
|
|
69
|
+
name,
|
|
70
|
+
},
|
|
71
|
+
relationships: {
|
|
72
|
+
snapshot: {
|
|
73
|
+
data: { type: "snapshots", id: snapshot_id },
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
try {
|
|
79
|
+
const result = (await client.post("/variants", body));
|
|
80
|
+
const id = result?.data?.id ?? "?";
|
|
81
|
+
return {
|
|
82
|
+
content: [
|
|
83
|
+
{
|
|
84
|
+
type: "text",
|
|
85
|
+
text: `Variant created (ID: ${id}, name: "${name}").`,
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
92
|
+
return {
|
|
93
|
+
content: [
|
|
94
|
+
{ type: "text", text: `Failed to create variant: ${message}` },
|
|
95
|
+
],
|
|
96
|
+
isError: true,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// ---- Update ----
|
|
101
|
+
if (action === "update") {
|
|
102
|
+
if (!variant_id) {
|
|
103
|
+
return {
|
|
104
|
+
content: [
|
|
105
|
+
{
|
|
106
|
+
type: "text",
|
|
107
|
+
text: "variant_id is required for the 'update' action.",
|
|
108
|
+
},
|
|
109
|
+
],
|
|
110
|
+
isError: true,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
const attrs = {};
|
|
114
|
+
if (name)
|
|
115
|
+
attrs.name = name;
|
|
116
|
+
if (state)
|
|
117
|
+
attrs.state = state;
|
|
118
|
+
const body = {
|
|
119
|
+
data: {
|
|
120
|
+
type: "variants",
|
|
121
|
+
id: variant_id,
|
|
122
|
+
attributes: attrs,
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
try {
|
|
126
|
+
await client.patch(`/variants/${variant_id}`, body);
|
|
127
|
+
return {
|
|
128
|
+
content: [
|
|
129
|
+
{
|
|
130
|
+
type: "text",
|
|
131
|
+
text: `Variant ${variant_id} updated.`,
|
|
132
|
+
},
|
|
133
|
+
],
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
138
|
+
return {
|
|
139
|
+
content: [
|
|
140
|
+
{ type: "text", text: `Failed to update variant: ${message}` },
|
|
141
|
+
],
|
|
142
|
+
isError: true,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return {
|
|
147
|
+
content: [
|
|
148
|
+
{
|
|
149
|
+
type: "text",
|
|
150
|
+
text: `Invalid action "${action}". Valid actions: list, create, update`,
|
|
151
|
+
},
|
|
152
|
+
],
|
|
153
|
+
isError: true,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* percy_manage_visual_monitoring — Create, update, or list Visual Monitoring projects
|
|
3
|
+
* with URL lists, cron schedules, and auth configuration.
|
|
4
|
+
*/
|
|
5
|
+
import { BrowserStackConfig } from "../../../lib/types.js";
|
|
6
|
+
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
7
|
+
interface ManageVisualMonitoringArgs {
|
|
8
|
+
org_id?: string;
|
|
9
|
+
project_id?: string;
|
|
10
|
+
action?: string;
|
|
11
|
+
urls?: string;
|
|
12
|
+
cron?: string;
|
|
13
|
+
schedule?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export declare function percyManageVisualMonitoring(args: ManageVisualMonitoringArgs, config: BrowserStackConfig): Promise<CallToolResult>;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* percy_manage_visual_monitoring — Create, update, or list Visual Monitoring projects
|
|
3
|
+
* with URL lists, cron schedules, and auth configuration.
|
|
4
|
+
*/
|
|
5
|
+
import { PercyClient } from "../../../lib/percy-api/client.js";
|
|
6
|
+
export async function percyManageVisualMonitoring(args, config) {
|
|
7
|
+
const { org_id, project_id, action = "list", urls, cron, schedule } = args;
|
|
8
|
+
const client = new PercyClient(config);
|
|
9
|
+
// ---- List ----
|
|
10
|
+
if (action === "list") {
|
|
11
|
+
if (!org_id) {
|
|
12
|
+
return {
|
|
13
|
+
content: [
|
|
14
|
+
{ type: "text", text: "org_id is required for the 'list' action." },
|
|
15
|
+
],
|
|
16
|
+
isError: true,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
const response = await client.get(`/organizations/${org_id}/visual_monitoring_projects`);
|
|
20
|
+
const projects = Array.isArray(response?.data) ? response.data : [];
|
|
21
|
+
if (projects.length === 0) {
|
|
22
|
+
return {
|
|
23
|
+
content: [
|
|
24
|
+
{
|
|
25
|
+
type: "text",
|
|
26
|
+
text: "_No Visual Monitoring projects found._",
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
const lines = [];
|
|
32
|
+
lines.push(`## Visual Monitoring Projects (Org: ${org_id})`);
|
|
33
|
+
lines.push("");
|
|
34
|
+
lines.push("| ID | Name | URLs | Schedule | Status |");
|
|
35
|
+
lines.push("|----|------|------|----------|--------|");
|
|
36
|
+
for (const project of projects) {
|
|
37
|
+
const attrs = project.attributes ?? project;
|
|
38
|
+
const name = attrs.name ?? "Unnamed";
|
|
39
|
+
const urlCount = Array.isArray(attrs.urls)
|
|
40
|
+
? attrs.urls.length
|
|
41
|
+
: (attrs["url-count"] ?? "?");
|
|
42
|
+
const cronSchedule = attrs.cron ?? attrs["cron-schedule"] ?? "—";
|
|
43
|
+
const status = attrs.enabled ?? attrs.status ?? "—";
|
|
44
|
+
lines.push(`| ${project.id ?? "?"} | ${name} | ${urlCount} URLs | ${cronSchedule} | ${status} |`);
|
|
45
|
+
}
|
|
46
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
47
|
+
}
|
|
48
|
+
// ---- Create ----
|
|
49
|
+
if (action === "create") {
|
|
50
|
+
if (!org_id) {
|
|
51
|
+
return {
|
|
52
|
+
content: [
|
|
53
|
+
{ type: "text", text: "org_id is required for the 'create' action." },
|
|
54
|
+
],
|
|
55
|
+
isError: true,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
const urlArray = urls
|
|
59
|
+
? urls
|
|
60
|
+
.split(",")
|
|
61
|
+
.map((u) => u.trim())
|
|
62
|
+
.filter(Boolean)
|
|
63
|
+
: [];
|
|
64
|
+
const attrs = {};
|
|
65
|
+
if (urlArray.length > 0)
|
|
66
|
+
attrs.urls = urlArray;
|
|
67
|
+
if (cron)
|
|
68
|
+
attrs.cron = cron;
|
|
69
|
+
if (schedule !== undefined)
|
|
70
|
+
attrs.enabled = schedule;
|
|
71
|
+
const body = {
|
|
72
|
+
data: {
|
|
73
|
+
type: "visual-monitoring-projects",
|
|
74
|
+
attributes: attrs,
|
|
75
|
+
relationships: {
|
|
76
|
+
organization: {
|
|
77
|
+
data: { type: "organizations", id: org_id },
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
try {
|
|
83
|
+
const result = (await client.post(`/organizations/${org_id}/visual_monitoring_projects`, body));
|
|
84
|
+
const id = result?.data?.id ?? "?";
|
|
85
|
+
return {
|
|
86
|
+
content: [
|
|
87
|
+
{
|
|
88
|
+
type: "text",
|
|
89
|
+
text: `## Visual Monitoring Project Created\n\n**ID:** ${id}\n**URLs:** ${urlArray.length}\n**Cron:** ${cron ?? "not set"}\n**Enabled:** ${schedule ?? "default"}`,
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
96
|
+
return {
|
|
97
|
+
content: [
|
|
98
|
+
{
|
|
99
|
+
type: "text",
|
|
100
|
+
text: `Failed to create Visual Monitoring project: ${message}`,
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
isError: true,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// ---- Update ----
|
|
108
|
+
if (action === "update") {
|
|
109
|
+
if (!project_id) {
|
|
110
|
+
return {
|
|
111
|
+
content: [
|
|
112
|
+
{
|
|
113
|
+
type: "text",
|
|
114
|
+
text: "project_id is required for the 'update' action.",
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
isError: true,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
const attrs = {};
|
|
121
|
+
if (urls) {
|
|
122
|
+
attrs.urls = urls
|
|
123
|
+
.split(",")
|
|
124
|
+
.map((u) => u.trim())
|
|
125
|
+
.filter(Boolean);
|
|
126
|
+
}
|
|
127
|
+
if (cron)
|
|
128
|
+
attrs.cron = cron;
|
|
129
|
+
if (schedule !== undefined)
|
|
130
|
+
attrs.enabled = schedule;
|
|
131
|
+
const body = {
|
|
132
|
+
data: {
|
|
133
|
+
type: "visual-monitoring-projects",
|
|
134
|
+
id: project_id,
|
|
135
|
+
attributes: attrs,
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
try {
|
|
139
|
+
await client.patch(`/visual_monitoring_projects/${project_id}`, body);
|
|
140
|
+
return {
|
|
141
|
+
content: [
|
|
142
|
+
{
|
|
143
|
+
type: "text",
|
|
144
|
+
text: `Visual Monitoring project ${project_id} updated.`,
|
|
145
|
+
},
|
|
146
|
+
],
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
151
|
+
return {
|
|
152
|
+
content: [
|
|
153
|
+
{
|
|
154
|
+
type: "text",
|
|
155
|
+
text: `Failed to update Visual Monitoring project: ${message}`,
|
|
156
|
+
},
|
|
157
|
+
],
|
|
158
|
+
isError: true,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
content: [
|
|
164
|
+
{
|
|
165
|
+
type: "text",
|
|
166
|
+
text: `Invalid action "${action}". Valid actions: list, create, update`,
|
|
167
|
+
},
|
|
168
|
+
],
|
|
169
|
+
isError: true,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { getPercyApiBaseUrl, maskToken } from "../../../lib/percy-api/auth.js";
|
|
2
|
+
import { PercyClient } from "../../../lib/percy-api/client.js";
|
|
3
|
+
import { getBrowserStackAuth } from "../../../lib/get-auth.js";
|
|
4
|
+
export async function percyAuthStatus(_args, config) {
|
|
5
|
+
const baseUrl = getPercyApiBaseUrl();
|
|
6
|
+
let output = `## Percy Auth Status\n\n`;
|
|
7
|
+
output += `**API URL:** ${baseUrl}\n\n`;
|
|
8
|
+
const percyToken = process.env.PERCY_TOKEN;
|
|
9
|
+
const orgToken = process.env.PERCY_ORG_TOKEN;
|
|
10
|
+
const hasBstackCreds = !!(config["browserstack-username"] && config["browserstack-access-key"]);
|
|
11
|
+
// Token table
|
|
12
|
+
output += `### Token Configuration\n\n`;
|
|
13
|
+
output += `| Token | Status | Value |\n`;
|
|
14
|
+
output += `|-------|--------|-------|\n`;
|
|
15
|
+
output += `| PERCY_TOKEN | ${percyToken ? "Set" : "Not set"} | ${percyToken ? maskToken(percyToken) : "—"} |\n`;
|
|
16
|
+
output += `| PERCY_ORG_TOKEN | ${orgToken ? "Set" : "Not set"} | ${orgToken ? maskToken(orgToken) : "—"} |\n`;
|
|
17
|
+
output += `| BrowserStack Credentials | ${hasBstackCreds ? "Set" : "Not set"} | ${hasBstackCreds ? config["browserstack-username"] : "—"} |\n`;
|
|
18
|
+
output += "\n";
|
|
19
|
+
// Detect token type from prefix
|
|
20
|
+
if (percyToken) {
|
|
21
|
+
const hasPrefix = percyToken.includes("_");
|
|
22
|
+
const prefix = hasPrefix ? percyToken.split("_")[0] : null;
|
|
23
|
+
const tokenTypes = {
|
|
24
|
+
web: "Web project (full access — can read and write)",
|
|
25
|
+
auto: "Automate project (full access)",
|
|
26
|
+
app: "App project (full access)",
|
|
27
|
+
ss: "Generic/BYOS project",
|
|
28
|
+
vmw: "Visual Monitoring project",
|
|
29
|
+
};
|
|
30
|
+
if (prefix && tokenTypes[prefix]) {
|
|
31
|
+
output += `**Token type:** ${prefix} — ${tokenTypes[prefix]}\n\n`;
|
|
32
|
+
}
|
|
33
|
+
else if (!hasPrefix) {
|
|
34
|
+
output += `**Token type:** CI/write-only — can create builds but may not read them\n`;
|
|
35
|
+
output += ` Tip: Use \`percy_create_project\` to get a full-access \`web_*\` token\n\n`;
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
output += `**Token type:** ${prefix} — custom\n\n`;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// Validation
|
|
42
|
+
output += `### Validation\n\n`;
|
|
43
|
+
// 1. Try Percy API with token
|
|
44
|
+
if (percyToken) {
|
|
45
|
+
try {
|
|
46
|
+
const client = new PercyClient(config, { scope: "project" });
|
|
47
|
+
const builds = await client.get("/builds", {
|
|
48
|
+
"page[limit]": "1",
|
|
49
|
+
});
|
|
50
|
+
const buildList = Array.isArray(builds) ? builds : [];
|
|
51
|
+
if (buildList.length > 0) {
|
|
52
|
+
const proj = buildList[0]?.project?.name ||
|
|
53
|
+
buildList[0]?.project?.slug ||
|
|
54
|
+
"unknown";
|
|
55
|
+
output += `**Percy API (read):** ✓ Valid — project "${proj}"\n`;
|
|
56
|
+
output += `**Latest build:** #${buildList[0]?.buildNumber || buildList[0]?.id} (${buildList[0]?.state || "unknown"})\n`;
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
output += `**Percy API (read):** ✓ Valid — no builds yet\n`;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// Read failed — token might be write-only (CI token)
|
|
64
|
+
output += `**Percy API (read):** ✗ No read access (this is normal for CI/write-only tokens)\n`;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// 2. Try BrowserStack API (project creation / token fetch)
|
|
68
|
+
if (hasBstackCreds) {
|
|
69
|
+
try {
|
|
70
|
+
const authString = getBrowserStackAuth(config);
|
|
71
|
+
const auth = Buffer.from(authString).toString("base64");
|
|
72
|
+
const response = await fetch("https://api.browserstack.com/api/app_percy/get_project_token?name=__mcp_auth_check__", {
|
|
73
|
+
headers: {
|
|
74
|
+
Authorization: `Basic ${auth}`,
|
|
75
|
+
"Content-Type": "application/json",
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
if (response.ok) {
|
|
79
|
+
output += `**BrowserStack API:** ✓ Valid — can create projects and get tokens\n`;
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
output += `**BrowserStack API:** ✗ Failed (${response.status})\n`;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
catch (e) {
|
|
86
|
+
output += `**BrowserStack API:** ✗ Error — ${e.message}\n`;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// 3. Org token check
|
|
90
|
+
if (orgToken) {
|
|
91
|
+
try {
|
|
92
|
+
const client = new PercyClient(config, { scope: "org" });
|
|
93
|
+
await client.get("/projects", { "page[limit]": "1" });
|
|
94
|
+
output += `**Org scope:** ✓ Valid\n`;
|
|
95
|
+
}
|
|
96
|
+
catch (e) {
|
|
97
|
+
output += `**Org scope:** ✗ Failed — ${e.message}\n`;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
output += "\n";
|
|
101
|
+
// Capabilities summary
|
|
102
|
+
output += `### What You Can Do\n\n`;
|
|
103
|
+
if (hasBstackCreds) {
|
|
104
|
+
output += `✓ **Create projects** — \`percy_create_project\`\n`;
|
|
105
|
+
output += `✓ **Create builds with snapshots** — \`percy_create_percy_build\`\n`;
|
|
106
|
+
}
|
|
107
|
+
if (percyToken) {
|
|
108
|
+
const hasPrefix = percyToken.includes("_");
|
|
109
|
+
const prefix = hasPrefix ? percyToken.split("_")[0] : null;
|
|
110
|
+
if (prefix === "web" || prefix === "auto" || prefix === "app") {
|
|
111
|
+
output += `✓ **Read builds, snapshots, comparisons** — all read tools\n`;
|
|
112
|
+
output += `✓ **Approve/reject builds** — \`percy_approve_build\`\n`;
|
|
113
|
+
output += `✓ **AI analysis, RCA, summaries** — all intelligence tools\n`;
|
|
114
|
+
output += `✓ **PR visual report** — \`percy_pr_visual_report\`\n`;
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
output += `⚠ **Limited read access** — this token can create builds but may not read them\n`;
|
|
118
|
+
output += ` Tip: Run \`percy_create_project\` to get a full-access \`web_*\` token\n`;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
else if (hasBstackCreds) {
|
|
122
|
+
output += `⚠ **No PERCY_TOKEN set** — read operations will use BrowserStack fallback\n`;
|
|
123
|
+
output += ` Tip: Run \`percy_create_project\` to get a project token\n`;
|
|
124
|
+
}
|
|
125
|
+
if (!percyToken && !orgToken && !hasBstackCreds) {
|
|
126
|
+
output += `### Setup Required\n\n`;
|
|
127
|
+
output += `No credentials configured. Run:\n`;
|
|
128
|
+
output += `\`\`\`bash\ncd mcp-server && ./percy-config/setup.sh\n\`\`\`\n`;
|
|
129
|
+
}
|
|
130
|
+
return { content: [{ type: "text", text: output }] };
|
|
131
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Percy build approval/rejection tool handler.
|
|
3
|
+
*
|
|
4
|
+
* Sends a review action (approve, request_changes, unapprove, reject)
|
|
5
|
+
* to the Percy Reviews API using JSON:API format.
|
|
6
|
+
*/
|
|
7
|
+
import { BrowserStackConfig } from "../../../lib/types.js";
|
|
8
|
+
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
9
|
+
export declare function percyApproveBuild(args: {
|
|
10
|
+
build_id: string;
|
|
11
|
+
action: string;
|
|
12
|
+
snapshot_ids?: string;
|
|
13
|
+
reason?: string;
|
|
14
|
+
}, config: BrowserStackConfig): Promise<CallToolResult>;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Percy build approval/rejection tool handler.
|
|
3
|
+
*
|
|
4
|
+
* Sends a review action (approve, request_changes, unapprove, reject)
|
|
5
|
+
* to the Percy Reviews API using JSON:API format.
|
|
6
|
+
*/
|
|
7
|
+
import { PercyClient } from "../../../lib/percy-api/client.js";
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// Constants
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
const VALID_ACTIONS = [
|
|
12
|
+
"approve",
|
|
13
|
+
"request_changes",
|
|
14
|
+
"unapprove",
|
|
15
|
+
"reject",
|
|
16
|
+
];
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Handler
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
export async function percyApproveBuild(args, config) {
|
|
21
|
+
const { build_id, action, snapshot_ids, reason } = args;
|
|
22
|
+
// Validate action
|
|
23
|
+
if (!VALID_ACTIONS.includes(action)) {
|
|
24
|
+
return {
|
|
25
|
+
content: [
|
|
26
|
+
{
|
|
27
|
+
type: "text",
|
|
28
|
+
text: `Invalid action "${action}". Valid actions: ${VALID_ACTIONS.join(", ")}`,
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
isError: true,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
// request_changes requires snapshot_ids (snapshot-level action)
|
|
35
|
+
if (action === "request_changes" && !snapshot_ids) {
|
|
36
|
+
return {
|
|
37
|
+
content: [
|
|
38
|
+
{
|
|
39
|
+
type: "text",
|
|
40
|
+
text: "request_changes requires snapshot_ids. This action works at snapshot level only.",
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
isError: true,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
// Build JSON:API request body
|
|
47
|
+
const body = {
|
|
48
|
+
data: {
|
|
49
|
+
type: "reviews",
|
|
50
|
+
attributes: {
|
|
51
|
+
action,
|
|
52
|
+
...(reason ? { reason } : {}),
|
|
53
|
+
},
|
|
54
|
+
relationships: {
|
|
55
|
+
build: {
|
|
56
|
+
data: { type: "builds", id: build_id },
|
|
57
|
+
},
|
|
58
|
+
...(snapshot_ids
|
|
59
|
+
? {
|
|
60
|
+
snapshots: {
|
|
61
|
+
data: snapshot_ids
|
|
62
|
+
.split(",")
|
|
63
|
+
.map((id) => id.trim())
|
|
64
|
+
.filter(Boolean)
|
|
65
|
+
.map((id) => ({ type: "snapshots", id })),
|
|
66
|
+
},
|
|
67
|
+
}
|
|
68
|
+
: {}),
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
try {
|
|
73
|
+
const client = new PercyClient(config);
|
|
74
|
+
const result = (await client.post("/reviews", body));
|
|
75
|
+
const reviewState = result?.reviewState ?? result?.["review-state"] ?? action;
|
|
76
|
+
return {
|
|
77
|
+
content: [
|
|
78
|
+
{
|
|
79
|
+
type: "text",
|
|
80
|
+
text: `Build #${build_id} ${action} successful. Review state: ${reviewState}`,
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
87
|
+
return {
|
|
88
|
+
content: [
|
|
89
|
+
{
|
|
90
|
+
type: "text",
|
|
91
|
+
text: `Failed to ${action} build #${build_id}: ${message}`,
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
isError: true,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
}
|