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.
Files changed (3) hide show
  1. package/README.md +8 -2
  2. package/package.json +3 -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 `verify_element`
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 et vérifier le rendu visuellement.
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.11.5",
5
- "description": "Serveur MCP pour piloter Bricks Builder (WordPress) depuis Claude — édition de pages, gestion d'éléments, réordonnancement des sections, vérification visuelle (verify_element), upload optimisé WebP. Communauté Discord : https://discord.gg/rX22zHRzH",
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.0.0",
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). Utile pour des animations, des polices spécifiques à cette page seulement, etc.",
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: { type: "number", description: "ID de la page" },
1049
- customCss: { type: "string", description: "CSS de la page (sera injecté dans <head> uniquement sur cette page)" },
1050
- customScripts: { type: "string", description: "Scripts/HTML de la page (injectés dans <head>)" },
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);