bricks-builder-mcp 3.11.5 → 3.12.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/README.md +8 -2
- package/package.json +3 -3
- package/server.js +480 -11
package/README.md
CHANGED
|
@@ -34,7 +34,7 @@ C'est la méthode **complète et la plus simple**. Tu obtiens : les outils MCP *
|
|
|
34
34
|
|
|
35
35
|
C'est fini. Aucun terminal, aucun JSON à éditer.
|
|
36
36
|
|
|
37
|
-
### (Optionnel mais recommandé) Activer
|
|
37
|
+
### (Optionnel mais recommandé) Activer les screenshots MCP
|
|
38
38
|
|
|
39
39
|
Dans un terminal, une seule fois :
|
|
40
40
|
|
|
@@ -42,7 +42,7 @@ Dans un terminal, une seule fois :
|
|
|
42
42
|
npx playwright install chromium
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
-
Ça télécharge Chromium (~130 Mo) pour que Claude puisse prendre des screenshots de tes pages
|
|
45
|
+
Ça télécharge Chromium (~130 Mo) pour que Claude/Codex puisse prendre des screenshots de tes pages avec `verify_element`, `audit_page` et `audit_design_page`.
|
|
46
46
|
|
|
47
47
|
### Test final
|
|
48
48
|
|
|
@@ -125,6 +125,12 @@ Claude Code détectera automatiquement le skill au prochain démarrage.
|
|
|
125
125
|
- Idéal pour : état initial avant refonte, démo client, audit après une grosse vague de modifs
|
|
126
126
|
- Checks intégrés : containers vides, images cassées + alt manquants, text-align mixé, débordement horizontal global de la page
|
|
127
127
|
|
|
128
|
+
**⭐ Audit design IA (v3.12+)**
|
|
129
|
+
- `audit_design_page` — dossier de revue visuelle pour l'IA : fullpage screenshots, crops de sections, contexte DOM/design et brief de critique.
|
|
130
|
+
- Objectif : détecter les défauts d'harmonie que les checks techniques ne voient pas
|
|
131
|
+
- Analyse attendue : hiérarchie, rythme, alignements, densité, largeur, typo, CTA, cohérence entre sections, sensation premium, responsive
|
|
132
|
+
- Ne remplace pas `audit_page` : workflow recommandé = `audit_page` pour les bugs techniques, puis `audit_design_page` pour la critique webdesign générale
|
|
133
|
+
|
|
128
134
|
**⭐ Upload optimisé (v3.8+)**
|
|
129
135
|
- `upload_local_file`, `upload_local_files_batch` — l'IA donne juste le path local, le MCP server lit le fichier, conversion WebP automatique (-80 à -95% de poids), renommage SEO via le `title`
|
|
130
136
|
- `upload_media`, `upload_media_batch` (accepte URL HTTP/HTTPS **ou** data URI)
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "bricks-builder-mcp",
|
|
4
|
-
"version": "3.
|
|
5
|
-
"description": "Serveur MCP pour piloter Bricks Builder (WordPress) depuis Claude — édition de pages, gestion d'éléments,
|
|
4
|
+
"version": "3.12.2",
|
|
5
|
+
"description": "Serveur MCP pour piloter Bricks Builder (WordPress) depuis Claude/Codex — édition de pages, gestion d'éléments, audit technique, audit design visuel, upload optimisé WebP. Communauté Discord : https://discord.gg/rX22zHRzH",
|
|
6
6
|
"homepage": "https://discord.gg/rX22zHRzH",
|
|
7
7
|
"main": "server.js",
|
|
8
8
|
"bin": {
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
],
|
|
15
15
|
"scripts": {
|
|
16
16
|
"start": "node server.js",
|
|
17
|
-
"postinstall": "node -e \"console.log('\\n[bricks-builder-mcp] Pour activer verify_element, lance UNE FOIS :\\n npx playwright install chromium\\n')\"",
|
|
17
|
+
"postinstall": "node -e \"console.log('\\n[bricks-builder-mcp] Pour activer verify_element/audit_page/audit_design_page, lance UNE FOIS :\\n npx playwright install chromium\\n')\"",
|
|
18
18
|
"test": "echo \"No tests yet\" && exit 0"
|
|
19
19
|
},
|
|
20
20
|
"keywords": [
|
package/server.js
CHANGED
|
@@ -356,7 +356,7 @@ console.error = (...args) => {
|
|
|
356
356
|
};
|
|
357
357
|
|
|
358
358
|
logToFile('========================================');
|
|
359
|
-
logToFile('DÉMARRAGE BRICKS MCP SERVER v3.0');
|
|
359
|
+
logToFile('DÉMARRAGE BRICKS MCP SERVER v3.12.0');
|
|
360
360
|
logToFile('========================================');
|
|
361
361
|
|
|
362
362
|
// Configuration WordPress
|
|
@@ -384,7 +384,7 @@ if (!process.env.WORDPRESS_URL || !process.env.API_KEY) {
|
|
|
384
384
|
const mcpServer = new Server(
|
|
385
385
|
{
|
|
386
386
|
name: "bricks-mcp-v3",
|
|
387
|
-
version: "3.
|
|
387
|
+
version: "3.12.0",
|
|
388
388
|
},
|
|
389
389
|
{
|
|
390
390
|
capabilities: {
|
|
@@ -570,7 +570,7 @@ mcpServer.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
570
570
|
|
|
571
571
|
{
|
|
572
572
|
name: "update_element",
|
|
573
|
-
description: "Modifie UN SEUL élément sans recharger/renvoyer toute la page. Ultra économe en tokens. Utilise pour changer une couleur, un texte, etc. Permet aussi de renommer l'élément dans la structure Bricks via le paramètre `label`.",
|
|
573
|
+
description: "Modifie UN SEUL élément sans recharger/renvoyer toute la page. Ultra économe en tokens. Utilise pour changer une couleur, un texte, etc. Permet aussi de renommer l'élément dans la structure Bricks via le paramètre `label`. Si un élément `code` exécutable est modifié, la réponse indique qu'une signature manuelle Bricks est requise.",
|
|
574
574
|
inputSchema: {
|
|
575
575
|
type: "object",
|
|
576
576
|
properties: {
|
|
@@ -597,7 +597,7 @@ mcpServer.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
597
597
|
|
|
598
598
|
{
|
|
599
599
|
name: "add_element",
|
|
600
|
-
description: "Ajoute UN SEUL nouvel élément à la page. Utilise pour ajouter un bouton, un paragraphe, etc.",
|
|
600
|
+
description: "Ajoute UN SEUL nouvel élément à la page. Utilise pour ajouter un bouton, un paragraphe, etc. Si `element.parent` pointe vers un élément existant, le parent.children est synchronisé automatiquement.",
|
|
601
601
|
inputSchema: {
|
|
602
602
|
type: "object",
|
|
603
603
|
properties: {
|
|
@@ -620,7 +620,7 @@ mcpServer.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
620
620
|
|
|
621
621
|
{
|
|
622
622
|
name: "batch_add",
|
|
623
|
-
description: "Ajoute PLUSIEURS éléments en UNE SEULE fois. Utilise pour créer une section complète (5-10 éléments). Plus efficace que add_element en boucle.",
|
|
623
|
+
description: "Ajoute PLUSIEURS éléments en UNE SEULE fois. Utilise pour créer une section complète (5-10 éléments). Synchronise automatiquement les parent.children des éléments ajoutés. Plus efficace que add_element en boucle.",
|
|
624
624
|
inputSchema: {
|
|
625
625
|
type: "object",
|
|
626
626
|
properties: {
|
|
@@ -828,6 +828,31 @@ mcpServer.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
828
828
|
description: "Récupère les settings globaux de Bricks Builder pour ce site (typographie globale, breakpoints, palette, classes globales, theme styles). Utile au début d'un projet pour comprendre la base visuelle existante.",
|
|
829
829
|
inputSchema: { type: "object", properties: {} },
|
|
830
830
|
},
|
|
831
|
+
{
|
|
832
|
+
name: "get_element_schema",
|
|
833
|
+
description: "Découvre les éléments Bricks disponibles et leurs contrôles natifs depuis le registre runtime Bricks. Sans `element`, retourne le catalogue compact. Avec `element` (ex: button, image, form), retourne les contrôles/settings de cet élément + les settings hérités communs si includeInherited=true.",
|
|
834
|
+
inputSchema: {
|
|
835
|
+
type: "object",
|
|
836
|
+
properties: {
|
|
837
|
+
element: {
|
|
838
|
+
type: "string",
|
|
839
|
+
description: "Nom technique Bricks de l'élément (ex: section, container, heading, text-basic, button, image). Si omis, retourne le catalogue compact.",
|
|
840
|
+
},
|
|
841
|
+
catalogOnly: {
|
|
842
|
+
type: "boolean",
|
|
843
|
+
description: "Forcer le mode catalogue compact même si element est absent. Défaut: true quand element est omis.",
|
|
844
|
+
},
|
|
845
|
+
includeInherited: {
|
|
846
|
+
type: "boolean",
|
|
847
|
+
description: "Inclure les contrôles de style communs (_padding, _typography, _background, etc.) avec un schema d'élément ciblé. Défaut: true.",
|
|
848
|
+
},
|
|
849
|
+
raw: {
|
|
850
|
+
type: "boolean",
|
|
851
|
+
description: "Inclure une version assainie des contrôles Bricks bruts. Plus lourd en tokens, à utiliser seulement pour debug.",
|
|
852
|
+
},
|
|
853
|
+
},
|
|
854
|
+
},
|
|
855
|
+
},
|
|
831
856
|
{
|
|
832
857
|
name: "update_global_styles",
|
|
833
858
|
description: "Met à jour les settings globaux Bricks via fusion récursive. Utile pour appliquer une typo de site ou une convention CSS partout d'un coup. Seuls les champs fournis sont modifiés.",
|
|
@@ -1041,13 +1066,16 @@ mcpServer.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
1041
1066
|
},
|
|
1042
1067
|
{
|
|
1043
1068
|
name: "set_page_custom_code",
|
|
1044
|
-
description: "Définit du CSS et/ou JS spécifique à une page (Page Settings → Custom Code).
|
|
1069
|
+
description: "Définit du CSS et/ou JS spécifique à une page (Page Settings → Custom Code). Utiliser les emplacements Bricks natifs : customScriptsHeader, customScriptsBodyHeader, customScriptsBodyFooter. `customScripts` reste un alias legacy vers body footer.",
|
|
1045
1070
|
inputSchema: {
|
|
1046
1071
|
type: "object",
|
|
1047
1072
|
properties: {
|
|
1048
|
-
pageId:
|
|
1049
|
-
customCss:
|
|
1050
|
-
|
|
1073
|
+
pageId: { type: "number", description: "ID de la page" },
|
|
1074
|
+
customCss: { type: "string", description: "CSS de la page (injecté uniquement sur cette page)" },
|
|
1075
|
+
customScriptsHeader: { type: "string", description: "HTML/scripts injectés dans <head> uniquement sur cette page" },
|
|
1076
|
+
customScriptsBodyHeader: { type: "string", description: "HTML/scripts injectés juste après <body> uniquement sur cette page" },
|
|
1077
|
+
customScriptsBodyFooter: { type: "string", description: "HTML/scripts injectés avant </body> uniquement sur cette page" },
|
|
1078
|
+
customScripts: { type: "string", description: "Alias legacy : écrit dans customScriptsBodyFooter" },
|
|
1051
1079
|
},
|
|
1052
1080
|
required: ["pageId"],
|
|
1053
1081
|
},
|
|
@@ -1176,6 +1204,46 @@ mcpServer.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
1176
1204
|
required: ["pageId"],
|
|
1177
1205
|
},
|
|
1178
1206
|
},
|
|
1207
|
+
{
|
|
1208
|
+
name: "audit_design_page",
|
|
1209
|
+
description: "⭐ AUDIT DESIGN GÉNÉRAL d'une page Bricks. Capture la page en multi-viewport, découpe les sections visibles, extrait le contexte DOM/design, puis retourne un dossier de revue visuelle pour l'IA. À utiliser pour détecter harmonie, hiérarchie, rythme, cohérence, sensation premium, responsive et zones qui font 'pas fini'. Ne remplace pas audit_page : il complète l'audit technique par une vraie passe webdesign.",
|
|
1210
|
+
inputSchema: {
|
|
1211
|
+
type: "object",
|
|
1212
|
+
properties: {
|
|
1213
|
+
pageId: { type: "number", description: "L'ID de la page WP à auditer" },
|
|
1214
|
+
viewports: {
|
|
1215
|
+
type: "array",
|
|
1216
|
+
items: { type: "string", enum: ["desktop", "tablet", "mobile_landscape", "mobile_portrait"] },
|
|
1217
|
+
description: "Viewports à capturer (défaut: ['desktop', 'mobile_portrait']).",
|
|
1218
|
+
},
|
|
1219
|
+
brief: {
|
|
1220
|
+
type: "string",
|
|
1221
|
+
description: "Contexte métier/design optionnel pour calibrer l'audit (ex: artisan carreleur premium local, objectif devis, cible particuliers).",
|
|
1222
|
+
},
|
|
1223
|
+
maxSections: {
|
|
1224
|
+
type: "number",
|
|
1225
|
+
description: "Nombre max de sections cropées par viewport (défaut: 8). Le fullpage reste inclus.",
|
|
1226
|
+
},
|
|
1227
|
+
maxCropHeight: {
|
|
1228
|
+
type: "number",
|
|
1229
|
+
description: "Hauteur max d'un crop de section en px (défaut: 1200) pour éviter des images trop lourdes.",
|
|
1230
|
+
},
|
|
1231
|
+
includeFullPage: {
|
|
1232
|
+
type: "boolean",
|
|
1233
|
+
description: "Inclure le screenshot fullpage propre par viewport (défaut: true).",
|
|
1234
|
+
},
|
|
1235
|
+
includeSectionCrops: {
|
|
1236
|
+
type: "boolean",
|
|
1237
|
+
description: "Inclure les crops de sections principales (défaut: true).",
|
|
1238
|
+
},
|
|
1239
|
+
sectionSelector: {
|
|
1240
|
+
type: "string",
|
|
1241
|
+
description: "Sélecteur CSS optionnel pour découper les sections (défaut: sections Bricks sous #brx-content, fallback main/body).",
|
|
1242
|
+
},
|
|
1243
|
+
},
|
|
1244
|
+
required: ["pageId"],
|
|
1245
|
+
},
|
|
1246
|
+
},
|
|
1179
1247
|
|
|
1180
1248
|
// ===== v3.6.0 — FEEDBACK SYSTEM (missing MCP features) =====
|
|
1181
1249
|
{
|
|
@@ -1638,6 +1706,16 @@ mcpServer.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1638
1706
|
result = await callWordPressAPI("/get-global-styles", "GET");
|
|
1639
1707
|
break;
|
|
1640
1708
|
|
|
1709
|
+
case "get_element_schema":
|
|
1710
|
+
console.error(`[LOG] Exécution: get_element_schema`);
|
|
1711
|
+
result = await callWordPressAPI("/get-element-schema", "POST", {
|
|
1712
|
+
element: args.element || "",
|
|
1713
|
+
catalogOnly: args.catalogOnly,
|
|
1714
|
+
includeInherited: args.includeInherited,
|
|
1715
|
+
raw: args.raw || false,
|
|
1716
|
+
});
|
|
1717
|
+
break;
|
|
1718
|
+
|
|
1641
1719
|
case "update_global_styles":
|
|
1642
1720
|
console.error(`[LOG] Exécution: update_global_styles`);
|
|
1643
1721
|
result = await callWordPressAPI("/update-global-styles", "POST", {
|
|
@@ -1773,6 +1851,9 @@ mcpServer.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1773
1851
|
result = await callWordPressAPI("/set-page-custom-code", "POST", {
|
|
1774
1852
|
pageId: args.pageId,
|
|
1775
1853
|
customCss: args.customCss,
|
|
1854
|
+
customScriptsHeader: args.customScriptsHeader,
|
|
1855
|
+
customScriptsBodyHeader: args.customScriptsBodyHeader,
|
|
1856
|
+
customScriptsBodyFooter: args.customScriptsBodyFooter,
|
|
1776
1857
|
customScripts: args.customScripts,
|
|
1777
1858
|
});
|
|
1778
1859
|
break;
|
|
@@ -2531,6 +2612,394 @@ mcpServer.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2531
2612
|
return { content: responseContent };
|
|
2532
2613
|
}
|
|
2533
2614
|
|
|
2615
|
+
// ===== v3.12 — AUDIT_DESIGN_PAGE (dossier de revue visuelle pour IA) =====
|
|
2616
|
+
case "audit_design_page": {
|
|
2617
|
+
const pageId = args.pageId;
|
|
2618
|
+
const viewportList = (Array.isArray(args.viewports) && args.viewports.length > 0)
|
|
2619
|
+
? args.viewports
|
|
2620
|
+
: ["desktop", "mobile_portrait"];
|
|
2621
|
+
const maxSections = Number.isFinite(args.maxSections)
|
|
2622
|
+
? Math.max(1, Math.min(24, Math.round(args.maxSections)))
|
|
2623
|
+
: 8;
|
|
2624
|
+
const maxCropHeight = Number.isFinite(args.maxCropHeight)
|
|
2625
|
+
? Math.max(400, Math.min(2400, Math.round(args.maxCropHeight)))
|
|
2626
|
+
: 1200;
|
|
2627
|
+
const includeFullPage = args.includeFullPage !== false;
|
|
2628
|
+
const includeSectionCrops = args.includeSectionCrops !== false;
|
|
2629
|
+
const sectionSelector = args.sectionSelector || "#brx-content > .brxe-section, main > .brxe-section, .brxe-section";
|
|
2630
|
+
const brief = args.brief || "";
|
|
2631
|
+
|
|
2632
|
+
// Récupérer l'URL de la page via /list-pages pour rester compatible avec les plugins déjà installés.
|
|
2633
|
+
const allPages = await callWordPressAPI("/list-pages", "GET");
|
|
2634
|
+
const pageMeta = (Array.isArray(allPages) ? allPages : []).find(p => p.id === pageId);
|
|
2635
|
+
if (!pageMeta) {
|
|
2636
|
+
return { content: [{ type: "text", text: JSON.stringify({ success: false, error: `Page ${pageId} introuvable dans list_bricks_pages`, hint: "Vérifie l'ID via list_bricks_pages." }, null, 2) }] };
|
|
2637
|
+
}
|
|
2638
|
+
const pageUrl = pageMeta.url;
|
|
2639
|
+
|
|
2640
|
+
const perViewport = [];
|
|
2641
|
+
const imageContent = [];
|
|
2642
|
+
|
|
2643
|
+
for (const viewport of viewportList) {
|
|
2644
|
+
let page;
|
|
2645
|
+
try {
|
|
2646
|
+
page = await getNewPage(viewport);
|
|
2647
|
+
} catch (browserErr) {
|
|
2648
|
+
perViewport.push({
|
|
2649
|
+
viewport,
|
|
2650
|
+
success: false,
|
|
2651
|
+
error: browserErr.message,
|
|
2652
|
+
hint: "Installation Chromium requise pour audit_design_page. Lance : npx playwright install chromium",
|
|
2653
|
+
});
|
|
2654
|
+
continue;
|
|
2655
|
+
}
|
|
2656
|
+
|
|
2657
|
+
try {
|
|
2658
|
+
await page.goto(pageUrl, { waitUntil: 'load', timeout: 30000 });
|
|
2659
|
+
await page.waitForTimeout(1000);
|
|
2660
|
+
|
|
2661
|
+
// Même stratégie que audit_page : déclencher les lazy-load avant de capturer.
|
|
2662
|
+
await page.evaluate(() => {
|
|
2663
|
+
document.querySelectorAll('img[loading="lazy"]').forEach(img => {
|
|
2664
|
+
img.loading = 'eager';
|
|
2665
|
+
});
|
|
2666
|
+
});
|
|
2667
|
+
await page.evaluate(() => {
|
|
2668
|
+
return new Promise(resolve => {
|
|
2669
|
+
const totalHeight = document.documentElement.scrollHeight;
|
|
2670
|
+
let scrolled = 0;
|
|
2671
|
+
const step = 450;
|
|
2672
|
+
const timer = setInterval(() => {
|
|
2673
|
+
window.scrollBy(0, step);
|
|
2674
|
+
scrolled += step;
|
|
2675
|
+
if (scrolled >= totalHeight) {
|
|
2676
|
+
clearInterval(timer);
|
|
2677
|
+
window.scrollTo(0, 0);
|
|
2678
|
+
setTimeout(resolve, 500);
|
|
2679
|
+
}
|
|
2680
|
+
}, 70);
|
|
2681
|
+
});
|
|
2682
|
+
});
|
|
2683
|
+
try {
|
|
2684
|
+
await page.waitForLoadState('networkidle', { timeout: 12000 });
|
|
2685
|
+
} catch {}
|
|
2686
|
+
await page.waitForTimeout(500);
|
|
2687
|
+
|
|
2688
|
+
const designContext = await page.evaluate(({ sectionSelector, maxSections }) => {
|
|
2689
|
+
const normalizeText = (value, max = 260) => {
|
|
2690
|
+
const text = String(value || '').replace(/\s+/g, ' ').trim();
|
|
2691
|
+
return text.length > max ? text.slice(0, max - 1) + '…' : text;
|
|
2692
|
+
};
|
|
2693
|
+
const num = (value) => Number.parseFloat(value) || 0;
|
|
2694
|
+
const bbox = (el) => {
|
|
2695
|
+
const rect = el.getBoundingClientRect();
|
|
2696
|
+
return {
|
|
2697
|
+
x: Math.round(rect.left + window.scrollX),
|
|
2698
|
+
y: Math.round(rect.top + window.scrollY),
|
|
2699
|
+
w: Math.round(rect.width),
|
|
2700
|
+
h: Math.round(rect.height),
|
|
2701
|
+
};
|
|
2702
|
+
};
|
|
2703
|
+
const isVisible = (el) => {
|
|
2704
|
+
if (!el || !el.getBoundingClientRect) return false;
|
|
2705
|
+
const rect = el.getBoundingClientRect();
|
|
2706
|
+
if (rect.width < 2 || rect.height < 2) return false;
|
|
2707
|
+
const cs = getComputedStyle(el);
|
|
2708
|
+
return cs.display !== 'none' && cs.visibility !== 'hidden' && num(cs.opacity) > 0.02;
|
|
2709
|
+
};
|
|
2710
|
+
const labelFor = (el) => {
|
|
2711
|
+
const tag = el.tagName.toLowerCase();
|
|
2712
|
+
const id = el.id ? `#${el.id}` : '';
|
|
2713
|
+
const classes = Array.from(el.classList || []).slice(0, 5).map(c => `.${c}`).join('');
|
|
2714
|
+
return `${tag}${id}${classes}`;
|
|
2715
|
+
};
|
|
2716
|
+
const pickComputed = (el) => {
|
|
2717
|
+
const cs = getComputedStyle(el);
|
|
2718
|
+
return {
|
|
2719
|
+
display: cs.display,
|
|
2720
|
+
position: cs.position,
|
|
2721
|
+
backgroundColor: cs.backgroundColor,
|
|
2722
|
+
color: cs.color,
|
|
2723
|
+
fontFamily: cs.fontFamily,
|
|
2724
|
+
fontSize: cs.fontSize,
|
|
2725
|
+
fontWeight: cs.fontWeight,
|
|
2726
|
+
lineHeight: cs.lineHeight,
|
|
2727
|
+
textAlign: cs.textAlign,
|
|
2728
|
+
padding: {
|
|
2729
|
+
top: cs.paddingTop,
|
|
2730
|
+
right: cs.paddingRight,
|
|
2731
|
+
bottom: cs.paddingBottom,
|
|
2732
|
+
left: cs.paddingLeft,
|
|
2733
|
+
},
|
|
2734
|
+
margin: {
|
|
2735
|
+
top: cs.marginTop,
|
|
2736
|
+
right: cs.marginRight,
|
|
2737
|
+
bottom: cs.marginBottom,
|
|
2738
|
+
left: cs.marginLeft,
|
|
2739
|
+
},
|
|
2740
|
+
gap: cs.gap,
|
|
2741
|
+
maxWidth: cs.maxWidth,
|
|
2742
|
+
width: cs.width,
|
|
2743
|
+
overflow: `${cs.overflowX}/${cs.overflowY}`,
|
|
2744
|
+
};
|
|
2745
|
+
};
|
|
2746
|
+
|
|
2747
|
+
let sectionNodes = [];
|
|
2748
|
+
try {
|
|
2749
|
+
sectionNodes = Array.from(document.querySelectorAll(sectionSelector)).filter(isVisible);
|
|
2750
|
+
} catch {}
|
|
2751
|
+
if (sectionNodes.length === 0) {
|
|
2752
|
+
const root = document.querySelector('#brx-content') || document.querySelector('main') || document.body;
|
|
2753
|
+
sectionNodes = Array.from(root.children || []).filter(isVisible);
|
|
2754
|
+
}
|
|
2755
|
+
|
|
2756
|
+
const seen = new Set();
|
|
2757
|
+
sectionNodes = sectionNodes.filter(el => {
|
|
2758
|
+
if (seen.has(el)) return false;
|
|
2759
|
+
seen.add(el);
|
|
2760
|
+
const rect = el.getBoundingClientRect();
|
|
2761
|
+
return rect.width >= 120 && rect.height >= 80;
|
|
2762
|
+
});
|
|
2763
|
+
|
|
2764
|
+
const pageHeadings = Array.from(document.querySelectorAll('h1,h2,h3,h4,h5,h6,.brxe-heading'))
|
|
2765
|
+
.filter(isVisible)
|
|
2766
|
+
.slice(0, 40)
|
|
2767
|
+
.map(el => {
|
|
2768
|
+
const cs = getComputedStyle(el);
|
|
2769
|
+
return {
|
|
2770
|
+
text: normalizeText(el.innerText || el.textContent, 120),
|
|
2771
|
+
tag: el.tagName.toLowerCase(),
|
|
2772
|
+
label: labelFor(el),
|
|
2773
|
+
fontSizePx: Math.round(num(cs.fontSize)),
|
|
2774
|
+
fontWeight: cs.fontWeight,
|
|
2775
|
+
lineHeight: cs.lineHeight,
|
|
2776
|
+
color: cs.color,
|
|
2777
|
+
bbox: bbox(el),
|
|
2778
|
+
};
|
|
2779
|
+
});
|
|
2780
|
+
|
|
2781
|
+
const pageButtons = Array.from(document.querySelectorAll('a,button,.brxe-button'))
|
|
2782
|
+
.filter(el => isVisible(el) && normalizeText(el.innerText || el.textContent, 80).length > 0)
|
|
2783
|
+
.slice(0, 40)
|
|
2784
|
+
.map(el => {
|
|
2785
|
+
const cs = getComputedStyle(el);
|
|
2786
|
+
return {
|
|
2787
|
+
text: normalizeText(el.innerText || el.textContent, 80),
|
|
2788
|
+
label: labelFor(el),
|
|
2789
|
+
fontSizePx: Math.round(num(cs.fontSize)),
|
|
2790
|
+
lineHeight: cs.lineHeight,
|
|
2791
|
+
whiteSpace: cs.whiteSpace,
|
|
2792
|
+
bbox: bbox(el),
|
|
2793
|
+
};
|
|
2794
|
+
});
|
|
2795
|
+
|
|
2796
|
+
const sections = sectionNodes.map((section, index) => {
|
|
2797
|
+
const headings = Array.from(section.querySelectorAll('h1,h2,h3,h4,h5,h6,.brxe-heading'))
|
|
2798
|
+
.filter(isVisible)
|
|
2799
|
+
.slice(0, 8)
|
|
2800
|
+
.map(el => {
|
|
2801
|
+
const cs = getComputedStyle(el);
|
|
2802
|
+
return {
|
|
2803
|
+
text: normalizeText(el.innerText || el.textContent, 140),
|
|
2804
|
+
tag: el.tagName.toLowerCase(),
|
|
2805
|
+
label: labelFor(el),
|
|
2806
|
+
fontSizePx: Math.round(num(cs.fontSize)),
|
|
2807
|
+
fontWeight: cs.fontWeight,
|
|
2808
|
+
lineHeight: cs.lineHeight,
|
|
2809
|
+
textAlign: cs.textAlign,
|
|
2810
|
+
bbox: bbox(el),
|
|
2811
|
+
};
|
|
2812
|
+
});
|
|
2813
|
+
|
|
2814
|
+
const ctas = Array.from(section.querySelectorAll('a,button,.brxe-button'))
|
|
2815
|
+
.filter(el => isVisible(el) && normalizeText(el.innerText || el.textContent, 80).length > 0)
|
|
2816
|
+
.slice(0, 8)
|
|
2817
|
+
.map(el => {
|
|
2818
|
+
const cs = getComputedStyle(el);
|
|
2819
|
+
return {
|
|
2820
|
+
text: normalizeText(el.innerText || el.textContent, 80),
|
|
2821
|
+
label: labelFor(el),
|
|
2822
|
+
fontSizePx: Math.round(num(cs.fontSize)),
|
|
2823
|
+
lineHeight: cs.lineHeight,
|
|
2824
|
+
whiteSpace: cs.whiteSpace,
|
|
2825
|
+
bbox: bbox(el),
|
|
2826
|
+
};
|
|
2827
|
+
});
|
|
2828
|
+
|
|
2829
|
+
const images = Array.from(section.querySelectorAll('img'))
|
|
2830
|
+
.filter(isVisible)
|
|
2831
|
+
.slice(0, 8)
|
|
2832
|
+
.map(img => ({
|
|
2833
|
+
src: img.currentSrc || img.src || '',
|
|
2834
|
+
alt: img.alt || '',
|
|
2835
|
+
natural: { width: img.naturalWidth || 0, height: img.naturalHeight || 0 },
|
|
2836
|
+
bbox: bbox(img),
|
|
2837
|
+
}));
|
|
2838
|
+
|
|
2839
|
+
const directChildren = Array.from(section.children || [])
|
|
2840
|
+
.filter(isVisible)
|
|
2841
|
+
.slice(0, 12)
|
|
2842
|
+
.map(el => ({
|
|
2843
|
+
label: labelFor(el),
|
|
2844
|
+
text: normalizeText(el.innerText || el.textContent, 90),
|
|
2845
|
+
bbox: bbox(el),
|
|
2846
|
+
display: getComputedStyle(el).display,
|
|
2847
|
+
}));
|
|
2848
|
+
|
|
2849
|
+
return {
|
|
2850
|
+
sectionNumber: index + 1,
|
|
2851
|
+
label: labelFor(section),
|
|
2852
|
+
id: section.id || null,
|
|
2853
|
+
classes: Array.from(section.classList || []),
|
|
2854
|
+
bbox: bbox(section),
|
|
2855
|
+
style: pickComputed(section),
|
|
2856
|
+
textPreview: normalizeText(section.innerText || section.textContent, 360),
|
|
2857
|
+
textLength: normalizeText(section.innerText || section.textContent, 100000).length,
|
|
2858
|
+
headings,
|
|
2859
|
+
ctas,
|
|
2860
|
+
images: {
|
|
2861
|
+
count: images.length,
|
|
2862
|
+
missingAlt: images.filter(i => !i.alt.trim()).length,
|
|
2863
|
+
samples: images,
|
|
2864
|
+
},
|
|
2865
|
+
directChildren,
|
|
2866
|
+
};
|
|
2867
|
+
});
|
|
2868
|
+
|
|
2869
|
+
return {
|
|
2870
|
+
title: document.title,
|
|
2871
|
+
url: location.href,
|
|
2872
|
+
pageDimensions: {
|
|
2873
|
+
width: document.documentElement.clientWidth,
|
|
2874
|
+
height: document.documentElement.scrollHeight,
|
|
2875
|
+
scrollWidth: document.documentElement.scrollWidth,
|
|
2876
|
+
},
|
|
2877
|
+
body: pickComputed(document.body),
|
|
2878
|
+
contentRoot: document.querySelector('#brx-content') ? '#brx-content' : (document.querySelector('main') ? 'main' : 'body'),
|
|
2879
|
+
sectionsTotal: sections.length,
|
|
2880
|
+
sections: sections.slice(0, maxSections),
|
|
2881
|
+
headingInventory: pageHeadings,
|
|
2882
|
+
ctaInventory: pageButtons,
|
|
2883
|
+
};
|
|
2884
|
+
}, { sectionSelector, maxSections });
|
|
2885
|
+
|
|
2886
|
+
const viewportImages = [];
|
|
2887
|
+
|
|
2888
|
+
if (includeFullPage) {
|
|
2889
|
+
const fullRef = `${viewport}:fullpage`;
|
|
2890
|
+
const buf = await page.screenshot({ type: 'jpeg', quality: 78, fullPage: true });
|
|
2891
|
+
imageContent.push({
|
|
2892
|
+
ref: fullRef,
|
|
2893
|
+
label: `${viewport} — page complète`,
|
|
2894
|
+
mimeType: "image/jpeg",
|
|
2895
|
+
data: buf.toString('base64'),
|
|
2896
|
+
});
|
|
2897
|
+
viewportImages.push({ ref: fullRef, kind: "fullpage" });
|
|
2898
|
+
}
|
|
2899
|
+
|
|
2900
|
+
if (includeSectionCrops) {
|
|
2901
|
+
const pageDimensions = designContext.pageDimensions || {};
|
|
2902
|
+
const pageWidth = Math.max(pageDimensions.scrollWidth || 0, pageDimensions.width || 0, 1);
|
|
2903
|
+
const pageHeight = Math.max(pageDimensions.height || 0, 1);
|
|
2904
|
+
const margin = 12;
|
|
2905
|
+
|
|
2906
|
+
for (const section of designContext.sections) {
|
|
2907
|
+
const box = section.bbox;
|
|
2908
|
+
if (!box || box.w < 20 || box.h < 20) continue;
|
|
2909
|
+
const clip = {
|
|
2910
|
+
x: Math.max(0, box.x - margin),
|
|
2911
|
+
y: Math.max(0, box.y - margin),
|
|
2912
|
+
width: Math.min(pageWidth - Math.max(0, box.x - margin), box.w + margin * 2),
|
|
2913
|
+
height: Math.min(pageHeight - Math.max(0, box.y - margin), Math.min(box.h + margin * 2, maxCropHeight)),
|
|
2914
|
+
};
|
|
2915
|
+
if (clip.width < 20 || clip.height < 20) continue;
|
|
2916
|
+
const ref = `${viewport}:section-${section.sectionNumber}`;
|
|
2917
|
+
try {
|
|
2918
|
+
const buf = await page.screenshot({ type: 'jpeg', quality: 82, clip });
|
|
2919
|
+
imageContent.push({
|
|
2920
|
+
ref,
|
|
2921
|
+
label: `${viewport} — section ${section.sectionNumber} — ${section.label}`,
|
|
2922
|
+
mimeType: "image/jpeg",
|
|
2923
|
+
data: buf.toString('base64'),
|
|
2924
|
+
});
|
|
2925
|
+
section.cropImageRef = ref;
|
|
2926
|
+
section.cropNote = box.h > maxCropHeight ? `Crop tronqué à ${maxCropHeight}px de haut.` : "Crop complet.";
|
|
2927
|
+
viewportImages.push({ ref, kind: "section", sectionNumber: section.sectionNumber });
|
|
2928
|
+
} catch (cropErr) {
|
|
2929
|
+
section.cropError = cropErr.message;
|
|
2930
|
+
}
|
|
2931
|
+
}
|
|
2932
|
+
}
|
|
2933
|
+
|
|
2934
|
+
perViewport.push({
|
|
2935
|
+
viewport,
|
|
2936
|
+
success: true,
|
|
2937
|
+
pageDimensions: designContext.pageDimensions,
|
|
2938
|
+
body: designContext.body,
|
|
2939
|
+
contentRoot: designContext.contentRoot,
|
|
2940
|
+
sectionsTotal: designContext.sectionsTotal,
|
|
2941
|
+
sectionsCaptured: designContext.sections.length,
|
|
2942
|
+
headingInventory: designContext.headingInventory,
|
|
2943
|
+
ctaInventory: designContext.ctaInventory,
|
|
2944
|
+
sections: designContext.sections,
|
|
2945
|
+
images: viewportImages,
|
|
2946
|
+
});
|
|
2947
|
+
} finally {
|
|
2948
|
+
if (page && !page.isClosed()) {
|
|
2949
|
+
await page.close().catch(() => {});
|
|
2950
|
+
}
|
|
2951
|
+
}
|
|
2952
|
+
}
|
|
2953
|
+
|
|
2954
|
+
const summary = {
|
|
2955
|
+
success: true,
|
|
2956
|
+
tool: "audit_design_page",
|
|
2957
|
+
pageId,
|
|
2958
|
+
pageUrl,
|
|
2959
|
+
pageTitle: pageMeta.title,
|
|
2960
|
+
brief,
|
|
2961
|
+
method: "Ce tool ne fait pas une liste de règles hardcodées. Il fournit screenshots + crops + contexte DOM pour une critique IA générale de webdesign.",
|
|
2962
|
+
reviewInstructionForAI: [
|
|
2963
|
+
"Inspecte les images avant de conclure. Ne te limite pas aux métriques DOM.",
|
|
2964
|
+
"Cherche les problèmes d'harmonie globale : hiérarchie, rythme vertical, densité, largeur de contenu, alignements, équilibre image/texte, cohérence entre sections, typographie, CTA, sensation premium, confiance, responsive.",
|
|
2965
|
+
"Signale aussi les zones qui semblent correctes techniquement mais faibles visuellement : trop template, vide mal placé, élément qui flotte, rupture de style, section trop lourde/légère, manque d'intention.",
|
|
2966
|
+
"Ne transforme pas chaque différence en bug. Priorise les points qu'un humain exigeant verrait en pleine page.",
|
|
2967
|
+
"Pour chaque finding : zone/section, viewport, sévérité, confiance, pourquoi ça nuit au design, correction proposée.",
|
|
2968
|
+
],
|
|
2969
|
+
suggestedOutputShape: {
|
|
2970
|
+
findings: [
|
|
2971
|
+
{
|
|
2972
|
+
severity: "critical|major|minor",
|
|
2973
|
+
confidence: "high|medium|low",
|
|
2974
|
+
viewport: "desktop|tablet|mobile_portrait",
|
|
2975
|
+
section: "section number or page area",
|
|
2976
|
+
issue: "description courte",
|
|
2977
|
+
whyItMatters: "impact design/UX",
|
|
2978
|
+
suggestedFix: "action concrète",
|
|
2979
|
+
},
|
|
2980
|
+
],
|
|
2981
|
+
globalRead: "lecture générale de la page",
|
|
2982
|
+
whatToValidateWithMathieu: "points subjectifs à faire trancher",
|
|
2983
|
+
},
|
|
2984
|
+
imageOrder: imageContent.map((img, index) => ({
|
|
2985
|
+
index: index + 1,
|
|
2986
|
+
ref: img.ref,
|
|
2987
|
+
label: img.label,
|
|
2988
|
+
})),
|
|
2989
|
+
viewports: perViewport,
|
|
2990
|
+
};
|
|
2991
|
+
|
|
2992
|
+
const responseContent = [
|
|
2993
|
+
{ type: "text", text: JSON.stringify(summary, null, 2) },
|
|
2994
|
+
];
|
|
2995
|
+
for (const img of imageContent) {
|
|
2996
|
+
responseContent.push({ type: "text", text: `Image ${imageContent.indexOf(img) + 1} — ${img.label} — ref: ${img.ref}` });
|
|
2997
|
+
responseContent.push({ type: "image", data: img.data, mimeType: img.mimeType });
|
|
2998
|
+
}
|
|
2999
|
+
|
|
3000
|
+
return { content: responseContent };
|
|
3001
|
+
}
|
|
3002
|
+
|
|
2534
3003
|
// ===== v3.6.0 — FEEDBACK SYSTEM =====
|
|
2535
3004
|
case "report_missing_feature":
|
|
2536
3005
|
result = await callWordPressAPI("/report-missing-feature", "POST", {
|
|
@@ -2731,7 +3200,7 @@ mcpServer.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2731
3200
|
async function main() {
|
|
2732
3201
|
const transport = new StdioServerTransport();
|
|
2733
3202
|
await mcpServer.connect(transport);
|
|
2734
|
-
console.error("[READY] MCP Bricks Builder v3.0 démarré et connecté");
|
|
3203
|
+
console.error("[READY] MCP Bricks Builder v3.12.0 démarré et connecté");
|
|
2735
3204
|
}
|
|
2736
3205
|
|
|
2737
|
-
main().catch(console.error);
|
|
3206
|
+
main().catch(console.error);
|