bricks-builder-mcp 3.6.3 → 3.6.5

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 (2) hide show
  1. package/package.json +1 -1
  2. package/server.js +103 -12
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "bricks-builder-mcp",
4
- "version": "3.6.3",
4
+ "version": "3.6.5",
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 + technique (verify_element).",
6
6
  "main": "server.js",
7
7
  "bin": {
package/server.js CHANGED
@@ -83,12 +83,66 @@ process.on('SIGINT', () => {
83
83
  process.exit(0);
84
84
  });
85
85
 
86
- // Normaliser les valeurs CSS pour comparaison (rgba/spaces/units)
87
- function normaliseCssValue(val) {
86
+ // Hex rgb/rgba pour matcher getComputedStyle qui renvoie toujours rgb()
87
+ function hexToRgb(hex) {
88
+ hex = hex.replace('#', '');
89
+ if (hex.length === 3) {
90
+ hex = hex.split('').map(c => c + c).join('');
91
+ }
92
+ if (hex.length !== 6 && hex.length !== 8) return null;
93
+ const r = parseInt(hex.substring(0, 2), 16);
94
+ const g = parseInt(hex.substring(2, 4), 16);
95
+ const b = parseInt(hex.substring(4, 6), 16);
96
+ if (isNaN(r) || isNaN(g) || isNaN(b)) return null;
97
+ if (hex.length === 8) {
98
+ const a = parseInt(hex.substring(6, 8), 16) / 255;
99
+ return `rgba(${r},${g},${b},${a.toFixed(2)})`;
100
+ }
101
+ return `rgb(${r},${g},${b})`;
102
+ }
103
+
104
+ // Normaliser une valeur rgba/rgb (supprime espaces, arrondit alpha)
105
+ function normaliseColor(s) {
106
+ s = s.replace(/\s+/g, '');
107
+ // rgba(0,0,0,0) → transparent
108
+ if (s === 'rgba(0,0,0,0)' || s === 'transparent') return 'transparent';
109
+ return s;
110
+ }
111
+
112
+ // Normaliser les valeurs CSS pour comparaison (rgba/spaces/units/hex/vh)
113
+ function normaliseCssValue(val, viewportContext) {
88
114
  if (val == null) return '';
89
115
  let s = String(val).trim().toLowerCase();
90
- // Supprime espaces internes (rgba(0, 0, 0) → rgba(0,0,0))
91
116
  s = s.replace(/\s+/g, '');
117
+
118
+ // Hex → rgb pour matcher getComputedStyle
119
+ if (/^#[0-9a-f]{3,8}$/.test(s)) {
120
+ const rgb = hexToRgb(s);
121
+ if (rgb) return normaliseColor(rgb);
122
+ }
123
+
124
+ // Couleurs rgb/rgba : normaliser pour comparer
125
+ if (s.startsWith('rgb')) {
126
+ return normaliseColor(s);
127
+ }
128
+
129
+ // vh / vw → px en fonction du viewport actuel (getComputedStyle renvoie en px)
130
+ if (viewportContext) {
131
+ const vhMatch = s.match(/^(\d+(?:\.\d+)?)vh$/);
132
+ if (vhMatch && viewportContext.height) {
133
+ return Math.round(parseFloat(vhMatch[1]) * viewportContext.height / 100) + 'px';
134
+ }
135
+ const vwMatch = s.match(/^(\d+(?:\.\d+)?)vw$/);
136
+ if (vwMatch && viewportContext.width) {
137
+ return Math.round(parseFloat(vwMatch[1]) * viewportContext.width / 100) + 'px';
138
+ }
139
+ // svh, dvh, lvh : on assimile à vh pour l'instant (approximatif mais OK pour la plupart des cas)
140
+ const svhMatch = s.match(/^(\d+(?:\.\d+)?)(?:svh|dvh|lvh)$/);
141
+ if (svhMatch && viewportContext.height) {
142
+ return Math.round(parseFloat(svhMatch[1]) * viewportContext.height / 100) + 'px';
143
+ }
144
+ }
145
+
92
146
  // Normalise "0px" et "0" et "0%"
93
147
  if (s === '0' || s === '0px' || s === '0%') return '0';
94
148
  // Si valeur sans unité fournie (ex "32") et getComputedStyle renvoie "32px"
@@ -1399,6 +1453,13 @@ mcpServer.setRequestHandler(CallToolRequestSchema, async (request) => {
1399
1453
  const pageId = args.pageId;
1400
1454
  const elementId = args.elementId;
1401
1455
  const viewport = args.viewport || "desktop";
1456
+ const viewportSizes = {
1457
+ desktop: { width: 1920, height: 1080 },
1458
+ tablet: { width: 991, height: 1200 },
1459
+ mobile_landscape: { width: 767, height: 600 },
1460
+ mobile_portrait: { width: 478, height: 800 },
1461
+ };
1462
+ const viewportContext = viewportSizes[viewport] || viewportSizes.desktop;
1402
1463
 
1403
1464
  // 1) Récupérer infos plugin (URL, sélecteur, expected styles)
1404
1465
  const info = await callWordPressAPI("/verify-element-info", "POST", { pageId, elementId });
@@ -1468,6 +1529,16 @@ mcpServer.setRequestHandler(CallToolRequestSchema, async (request) => {
1468
1529
  computed = await element.evaluate(el => {
1469
1530
  const cs = getComputedStyle(el);
1470
1531
  const rect = el.getBoundingClientRect();
1532
+ // Enfants "réels" = ceux qui ont une classe brxe-{id} ou id brxe-*
1533
+ // Exclut les <video>, <picture> et autres ajoutés par Bricks (bg vidéo, overlay auto, etc.)
1534
+ const realChildren = Array.from(el.children).filter(child => {
1535
+ if (child.id && child.id.startsWith('brxe-')) return true;
1536
+ return Array.from(child.classList).some(cls => cls.startsWith('brxe-'));
1537
+ });
1538
+ const bricksInternalChildren = Array.from(el.children).filter(child => {
1539
+ return !((child.id && child.id.startsWith('brxe-')) ||
1540
+ Array.from(child.classList).some(cls => cls.startsWith('brxe-')));
1541
+ }).map(c => c.tagName.toLowerCase() + (c.className ? '.' + c.className.split(' ').join('.') : ''));
1471
1542
  return {
1472
1543
  display: cs.display,
1473
1544
  'flex-direction': cs.flexDirection,
@@ -1500,7 +1571,9 @@ mcpServer.setRequestHandler(CallToolRequestSchema, async (request) => {
1500
1571
  'border-bottom-left-radius': cs.borderBottomLeftRadius,
1501
1572
  visibility: cs.visibility,
1502
1573
  opacity: cs.opacity,
1503
- childrenInDom: el.children.length,
1574
+ childrenInDom: realChildren.length,
1575
+ childrenTotalDom: el.children.length,
1576
+ bricksInternalChildren: bricksInternalChildren,
1504
1577
  isVisible: rect.width > 0 && rect.height > 0 && cs.visibility !== 'hidden' && cs.opacity !== '0',
1505
1578
  hasOverflowX: el.scrollWidth > el.clientWidth,
1506
1579
  };
@@ -1532,12 +1605,12 @@ mcpServer.setRequestHandler(CallToolRequestSchema, async (request) => {
1532
1605
  });
1533
1606
  }
1534
1607
 
1535
- // Compare chaque expected
1608
+ // Compare chaque expected (viewport-aware pour vh/vw)
1536
1609
  const expected = info.expected || {};
1537
1610
  for (const [prop, expectedVal] of Object.entries(expected)) {
1538
1611
  const got = computed[prop];
1539
1612
  if (got === undefined) continue;
1540
- const ok = normaliseCssValue(got) === normaliseCssValue(expectedVal);
1613
+ const ok = normaliseCssValue(got, viewportContext) === normaliseCssValue(expectedVal, viewportContext);
1541
1614
  const check = { ok, label: `${prop} = ${expectedVal}` };
1542
1615
  if (!ok) {
1543
1616
  check.expected = expectedVal;
@@ -1548,16 +1621,34 @@ mcpServer.setRequestHandler(CallToolRequestSchema, async (request) => {
1548
1621
  checks.push(check);
1549
1622
  }
1550
1623
 
1551
- // Console errors
1552
- if (consoleErrors.length > 0) {
1624
+ // Console errors : séparer les vrais bugs JS des erreurs réseau (429/timeout)
1625
+ // qui dépendent du serveur, pas du code de l'élément.
1626
+ const realErrors = consoleErrors.filter(e =>
1627
+ !e.includes('429') &&
1628
+ !e.includes('net::ERR_') &&
1629
+ !e.includes('Failed to load resource')
1630
+ );
1631
+ const networkErrors = consoleErrors.filter(e =>
1632
+ e.includes('429') || e.includes('net::ERR_') || e.includes('Failed to load resource')
1633
+ );
1634
+
1635
+ if (realErrors.length > 0) {
1553
1636
  checks.push({
1554
1637
  ok: false,
1555
- label: `${consoleErrors.length} erreur(s) console`,
1556
- got: consoleErrors.slice(0, 3),
1557
- hint: "Vérifie les ressources 404 (font, image, script)",
1638
+ label: `${realErrors.length} erreur(s) JS dans la console`,
1639
+ got: realErrors.slice(0, 3),
1640
+ hint: "Erreur JavaScript détectée pas lié au serveur",
1558
1641
  });
1559
1642
  } else {
1560
- checks.push({ ok: true, label: "Aucune erreur console" });
1643
+ checks.push({ ok: true, label: "Aucune erreur JS console" });
1644
+ }
1645
+ // Erreurs réseau : info seulement, pas un échec
1646
+ if (networkErrors.length > 0) {
1647
+ checks.push({
1648
+ ok: true,
1649
+ label: `${networkErrors.length} erreur(s) réseau (429/load) — non bloquant`,
1650
+ got: networkErrors.slice(0, 2),
1651
+ });
1561
1652
  }
1562
1653
 
1563
1654
  // Overflow horizontal