bricks-builder-mcp 3.5.0 → 3.6.0
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/package.json +5 -3
- package/server.js +442 -5
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, réordonnancement des sections.",
|
|
4
|
+
"version": "3.6.0",
|
|
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": {
|
|
8
8
|
"bricks-builder-mcp": "./server.js"
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
],
|
|
14
14
|
"scripts": {
|
|
15
15
|
"start": "node server.js",
|
|
16
|
+
"postinstall": "node -e \"console.log('\\n[bricks-builder-mcp] Pour activer verify_element, lance UNE FOIS :\\n npx playwright install chromium\\n')\"",
|
|
16
17
|
"test": "echo \"No tests yet\" && exit 0"
|
|
17
18
|
},
|
|
18
19
|
"keywords": [
|
|
@@ -28,6 +29,7 @@
|
|
|
28
29
|
"node": ">=18"
|
|
29
30
|
},
|
|
30
31
|
"dependencies": {
|
|
31
|
-
"@modelcontextprotocol/sdk": "^1.23.0"
|
|
32
|
+
"@modelcontextprotocol/sdk": "^1.23.0",
|
|
33
|
+
"playwright-core": "^1.50.0"
|
|
32
34
|
}
|
|
33
35
|
}
|
package/server.js
CHANGED
|
@@ -10,6 +10,112 @@ import fs from 'fs';
|
|
|
10
10
|
import { fileURLToPath } from 'url';
|
|
11
11
|
import { dirname, join } from 'path';
|
|
12
12
|
|
|
13
|
+
// ===== v3.6.0 — Playwright (lazy import pour verify_element) =====
|
|
14
|
+
// Chargé à la demande pour ne pas crasher si chromium pas installé.
|
|
15
|
+
let _chromium = null;
|
|
16
|
+
let _browser = null;
|
|
17
|
+
let _browserContext = null;
|
|
18
|
+
|
|
19
|
+
async function getChromium() {
|
|
20
|
+
if (_chromium) return _chromium;
|
|
21
|
+
try {
|
|
22
|
+
const mod = await import('playwright-core');
|
|
23
|
+
_chromium = mod.chromium;
|
|
24
|
+
return _chromium;
|
|
25
|
+
} catch (err) {
|
|
26
|
+
throw new Error(
|
|
27
|
+
"playwright-core introuvable. Réinstalle le package : npm i -g bricks-builder-mcp"
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function getBrowser() {
|
|
33
|
+
if (_browser && _browser.isConnected()) return _browser;
|
|
34
|
+
const chromium = await getChromium();
|
|
35
|
+
try {
|
|
36
|
+
_browser = await chromium.launch({ headless: true });
|
|
37
|
+
logToFile('[VERIFY] Browser Chromium lancé');
|
|
38
|
+
return _browser;
|
|
39
|
+
} catch (err) {
|
|
40
|
+
// Chromium pas installé
|
|
41
|
+
throw new Error(
|
|
42
|
+
"Chromium n'est pas installé pour Playwright.\n" +
|
|
43
|
+
"Lance UNE FOIS dans un terminal : npx playwright install chromium\n" +
|
|
44
|
+
"Détail : " + err.message
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function getNewPage(viewport) {
|
|
50
|
+
const browser = await getBrowser();
|
|
51
|
+
const sizes = {
|
|
52
|
+
desktop: { width: 1920, height: 1080 },
|
|
53
|
+
tablet: { width: 991, height: 1200 },
|
|
54
|
+
mobile_landscape: { width: 767, height: 600 },
|
|
55
|
+
mobile_portrait: { width: 478, height: 800 },
|
|
56
|
+
};
|
|
57
|
+
const size = sizes[viewport] || sizes.desktop;
|
|
58
|
+
if (!_browserContext) {
|
|
59
|
+
_browserContext = await browser.newContext({
|
|
60
|
+
viewport: size,
|
|
61
|
+
// Ignore SSL errors pour les sites pré-prod (cohérent avec INSECURE_SSL)
|
|
62
|
+
ignoreHTTPSErrors: INSECURE_SSL,
|
|
63
|
+
});
|
|
64
|
+
} else {
|
|
65
|
+
// Adapter le viewport si différent
|
|
66
|
+
}
|
|
67
|
+
const page = await _browserContext.newPage();
|
|
68
|
+
await page.setViewportSize(size);
|
|
69
|
+
return page;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Cleanup à exit
|
|
73
|
+
process.on('exit', () => {
|
|
74
|
+
if (_browser) {
|
|
75
|
+
try { _browser.close(); } catch {}
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
process.on('SIGINT', () => {
|
|
79
|
+
if (_browser) {
|
|
80
|
+
try { _browser.close(); } catch {}
|
|
81
|
+
}
|
|
82
|
+
process.exit(0);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// Normaliser les valeurs CSS pour comparaison (rgba/spaces/units)
|
|
86
|
+
function normaliseCssValue(val) {
|
|
87
|
+
if (val == null) return '';
|
|
88
|
+
let s = String(val).trim().toLowerCase();
|
|
89
|
+
// Supprime espaces internes (rgba(0, 0, 0) → rgba(0,0,0))
|
|
90
|
+
s = s.replace(/\s+/g, '');
|
|
91
|
+
// Normalise "0px" et "0" et "0%"
|
|
92
|
+
if (s === '0' || s === '0px' || s === '0%') return '0';
|
|
93
|
+
// Si valeur sans unité fournie (ex "32") et getComputedStyle renvoie "32px"
|
|
94
|
+
if (/^\d+(\.\d+)?$/.test(s)) s += 'px';
|
|
95
|
+
return s;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function generateHint(prop, expected, got) {
|
|
99
|
+
if (prop === 'gap' || prop === 'column-gap' || prop === 'row-gap') {
|
|
100
|
+
if (got === '0' || got === '0px' || got === 'normal') {
|
|
101
|
+
return "Ajouter les 3 propriétés : _gap + _columnGap + _rowGap (cf bricks-2.3-formats.md)";
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (prop === 'flex-direction' && got === 'column' && expected === 'row') {
|
|
105
|
+
return "Ajouter _direction: 'row' explicitement (défaut Bricks = column)";
|
|
106
|
+
}
|
|
107
|
+
if (prop.startsWith('border-') && prop.endsWith('-radius')) {
|
|
108
|
+
return "Utiliser _border.radius (imbriqué) — PAS _borderRadius flat";
|
|
109
|
+
}
|
|
110
|
+
if (prop === 'font-family' && got.includes('Times') || got.includes('serif')) {
|
|
111
|
+
return "Police pas chargée — set_custom_code({customScriptsHeader: '<link Google Fonts>'})";
|
|
112
|
+
}
|
|
113
|
+
if (prop === 'background-color' && got === 'rgba(0,0,0,0)') {
|
|
114
|
+
return "Background transparent — utiliser {color: {raw: 'rgba(...)'}} pour rgba (PAS hex)";
|
|
115
|
+
}
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
|
|
13
119
|
// Configuration du fichier de log
|
|
14
120
|
const __filename = fileURLToPath(import.meta.url);
|
|
15
121
|
const __dirname = dirname(__filename);
|
|
@@ -250,7 +356,7 @@ mcpServer.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
250
356
|
|
|
251
357
|
{
|
|
252
358
|
name: "update_element",
|
|
253
|
-
description: "Modifie UN SEUL élément sans recharger/renvoyer toute la page. Ultra économe en tokens. Utilise pour changer une couleur, un texte, etc.",
|
|
359
|
+
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`.",
|
|
254
360
|
inputSchema: {
|
|
255
361
|
type: "object",
|
|
256
362
|
properties: {
|
|
@@ -264,10 +370,14 @@ mcpServer.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
264
370
|
},
|
|
265
371
|
newSettings: {
|
|
266
372
|
type: "object",
|
|
267
|
-
description: "Les nouveaux settings (fusionnés avec les anciens)",
|
|
373
|
+
description: "Les nouveaux settings (fusionnés avec les anciens). Optionnel si label est fourni seul.",
|
|
374
|
+
},
|
|
375
|
+
label: {
|
|
376
|
+
type: "string",
|
|
377
|
+
description: "Nom affiché dans la structure du builder Bricks (ex: 'Hero Section', 'Header Pill', 'Logo', 'CTA Buttons'). Modifie l'attribut `label` au niveau racine de l'élément. Optionnel.",
|
|
268
378
|
},
|
|
269
379
|
},
|
|
270
|
-
required: ["pageId", "elementId"
|
|
380
|
+
required: ["pageId", "elementId"],
|
|
271
381
|
},
|
|
272
382
|
},
|
|
273
383
|
|
|
@@ -787,6 +897,93 @@ mcpServer.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
787
897
|
description: "Liste les components Bricks (templates avec type=component, Bricks 2.x).",
|
|
788
898
|
inputSchema: { type: "object", properties: {} },
|
|
789
899
|
},
|
|
900
|
+
|
|
901
|
+
// ===== v3.6.0 — VERIFY ELEMENT (vérification visuelle + technique) =====
|
|
902
|
+
{
|
|
903
|
+
name: "verify_element",
|
|
904
|
+
description: "⭐ APRÈS CHAQUE batch_add OU update_element SIGNIFICATIF, utilise cet outil. Lance un browser headless, navigue sur la page, scroll vers l'élément, prend un screenshot crop et compare les computed styles avec les settings attendus. Retourne {screenshot (visible par Claude), report: {score, checks}, computed}. Tu vois ce que tu as fait. Force l'évolution petit-à-petit-vérifier-petit-à-petit.",
|
|
905
|
+
inputSchema: {
|
|
906
|
+
type: "object",
|
|
907
|
+
properties: {
|
|
908
|
+
pageId: { type: "number", description: "L'ID de la page" },
|
|
909
|
+
elementId: { type: "string", description: "L'ID de l'élément à vérifier (ex: 'section_abc')" },
|
|
910
|
+
viewport: {
|
|
911
|
+
type: "string",
|
|
912
|
+
enum: ["desktop", "tablet", "mobile_landscape", "mobile_portrait"],
|
|
913
|
+
description: "Taille d'écran à tester (défaut: desktop)",
|
|
914
|
+
},
|
|
915
|
+
},
|
|
916
|
+
required: ["pageId", "elementId"],
|
|
917
|
+
},
|
|
918
|
+
},
|
|
919
|
+
|
|
920
|
+
// ===== v3.6.0 — FEEDBACK SYSTEM (missing MCP features) =====
|
|
921
|
+
{
|
|
922
|
+
name: "report_missing_feature",
|
|
923
|
+
description: "À UTILISER UNIQUEMENT quand Bricks supporte une feature nativement (vérifié via doc officielle) MAIS le MCP ne l'expose pas correctement (outil manquant / buggy / setting ignoré). PAS pour les limites Bricks elles-mêmes — dans ce cas code une alternative libre (CSS/JS via set_page_custom_code). Le gestionnaire du MCP lit ces feedbacks pour combler les trous.",
|
|
924
|
+
inputSchema: {
|
|
925
|
+
type: "object",
|
|
926
|
+
properties: {
|
|
927
|
+
title: { type: "string", description: "Titre court du manque (ex: 'Pas d outil pour Interactions Bricks')" },
|
|
928
|
+
bricksFeature: { type: "string", description: "Nom officiel Bricks de la feature (ex: 'Interactions API')" },
|
|
929
|
+
bricksDocUrl: { type: "string", description: "Lien vers la doc Bricks qui prouve que la feature est native" },
|
|
930
|
+
whatItShouldDo: { type: "string", description: "Ce que l'outil MCP devrait faire" },
|
|
931
|
+
whatITried: { type: "string", description: "Ce que tu as tenté avec les outils actuels et le résultat" },
|
|
932
|
+
proposedTool: { type: "string", description: "Nom d'outil suggéré (ex: 'set_element_interactions')" },
|
|
933
|
+
bricksVersion: { type: "string", description: "Version Bricks du site (ex: '2.3.2')" },
|
|
934
|
+
context: { type: "string", description: "Contexte du chat (URL page, ce que tu construisais)" },
|
|
935
|
+
},
|
|
936
|
+
required: ["title", "bricksFeature"],
|
|
937
|
+
},
|
|
938
|
+
},
|
|
939
|
+
{
|
|
940
|
+
name: "list_missing_features",
|
|
941
|
+
description: "Liste les feedbacks remontés par d'autres chats. Pour le mainteneur du MCP qui veut savoir quoi prioriser.",
|
|
942
|
+
inputSchema: {
|
|
943
|
+
type: "object",
|
|
944
|
+
properties: {
|
|
945
|
+
status: { type: "string", enum: ["open", "resolved"], description: "Filtrer par statut (défaut: tous)" },
|
|
946
|
+
},
|
|
947
|
+
},
|
|
948
|
+
},
|
|
949
|
+
{
|
|
950
|
+
name: "resolve_missing_feature",
|
|
951
|
+
description: "Marque un feedback comme résolu (nouvel outil ajouté ou doc enrichie).",
|
|
952
|
+
inputSchema: {
|
|
953
|
+
type: "object",
|
|
954
|
+
properties: {
|
|
955
|
+
id: { type: "string", description: "ID du feedback à résoudre" },
|
|
956
|
+
resolutionNote: { type: "string", description: "Comment c'est résolu (nouvel outil, doc, version)" },
|
|
957
|
+
},
|
|
958
|
+
required: ["id"],
|
|
959
|
+
},
|
|
960
|
+
},
|
|
961
|
+
|
|
962
|
+
// ===== v3.6.0 — UPLOAD MEDIA BATCH =====
|
|
963
|
+
{
|
|
964
|
+
name: "upload_media_batch",
|
|
965
|
+
description: "Upload plusieurs images en 1 appel (vs upload_media qui en fait 1 à la fois). Continue même si certaines échouent. Retourne {uploaded, failed, successes: [{id, url, sourceUrl, filename}], failures: [{sourceUrl, error}]}.",
|
|
966
|
+
inputSchema: {
|
|
967
|
+
type: "object",
|
|
968
|
+
properties: {
|
|
969
|
+
items: {
|
|
970
|
+
type: "array",
|
|
971
|
+
description: "Liste des images à uploader",
|
|
972
|
+
items: {
|
|
973
|
+
type: "object",
|
|
974
|
+
properties: {
|
|
975
|
+
sourceUrl: { type: "string", description: "URL source de l'image" },
|
|
976
|
+
title: { type: "string", description: "Titre WP (sert aussi de nom de fichier slugifié)" },
|
|
977
|
+
alt: { type: "string", description: "Texte alternatif (SEO + accessibilité)" },
|
|
978
|
+
caption: { type: "string", description: "Légende optionnelle" },
|
|
979
|
+
},
|
|
980
|
+
required: ["sourceUrl"],
|
|
981
|
+
},
|
|
982
|
+
},
|
|
983
|
+
},
|
|
984
|
+
required: ["items"],
|
|
985
|
+
},
|
|
986
|
+
},
|
|
790
987
|
],
|
|
791
988
|
};
|
|
792
989
|
});
|
|
@@ -865,11 +1062,12 @@ mcpServer.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
865
1062
|
|
|
866
1063
|
case "update_element":
|
|
867
1064
|
console.error(`[LOG] Exécution: update_element`);
|
|
868
|
-
console.error(`[LOG] Modification de l'élément ${args.elementId}`);
|
|
1065
|
+
console.error(`[LOG] Modification de l'élément ${args.elementId}${args.label ? ` (label: "${args.label}")` : ''}`);
|
|
869
1066
|
result = await callWordPressAPI("/update-element", "POST", {
|
|
870
1067
|
pageId: args.pageId,
|
|
871
1068
|
elementId: args.elementId,
|
|
872
|
-
newSettings: args.newSettings,
|
|
1069
|
+
newSettings: args.newSettings || {},
|
|
1070
|
+
label: args.label,
|
|
873
1071
|
});
|
|
874
1072
|
break;
|
|
875
1073
|
|
|
@@ -1195,6 +1393,245 @@ mcpServer.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1195
1393
|
result = await callWordPressAPI("/list-components", "GET");
|
|
1196
1394
|
break;
|
|
1197
1395
|
|
|
1396
|
+
// ===== v3.6.0 — VERIFY ELEMENT =====
|
|
1397
|
+
case "verify_element": {
|
|
1398
|
+
const pageId = args.pageId;
|
|
1399
|
+
const elementId = args.elementId;
|
|
1400
|
+
const viewport = args.viewport || "desktop";
|
|
1401
|
+
|
|
1402
|
+
// 1) Récupérer infos plugin (URL, sélecteur, expected styles)
|
|
1403
|
+
const info = await callWordPressAPI("/verify-element-info", "POST", { pageId, elementId });
|
|
1404
|
+
|
|
1405
|
+
// 2) Lancer browser headless
|
|
1406
|
+
let page;
|
|
1407
|
+
try {
|
|
1408
|
+
page = await getNewPage(viewport);
|
|
1409
|
+
} catch (browserErr) {
|
|
1410
|
+
// Erreur Playwright (chromium pas installé etc) — retourner sans crasher
|
|
1411
|
+
result = {
|
|
1412
|
+
success: false,
|
|
1413
|
+
error: browserErr.message,
|
|
1414
|
+
url: info.url,
|
|
1415
|
+
selector: info.selector,
|
|
1416
|
+
hint: "Installation Chromium requise pour verify_element. À défaut, utilise screenshot-website-fast en MCP externe.",
|
|
1417
|
+
};
|
|
1418
|
+
break;
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
// Collecte console errors
|
|
1422
|
+
const consoleErrors = [];
|
|
1423
|
+
page.on('console', msg => {
|
|
1424
|
+
if (msg.type() === 'error') consoleErrors.push(msg.text());
|
|
1425
|
+
});
|
|
1426
|
+
page.on('pageerror', err => consoleErrors.push('PageError: ' + err.message));
|
|
1427
|
+
|
|
1428
|
+
let screenshotBase64 = null;
|
|
1429
|
+
let report = { score: '0/0', checks: [] };
|
|
1430
|
+
let computed = null;
|
|
1431
|
+
let loadedFonts = [];
|
|
1432
|
+
|
|
1433
|
+
try {
|
|
1434
|
+
await page.goto(info.url, { waitUntil: 'domcontentloaded', timeout: 30000 });
|
|
1435
|
+
// Attendre 2s pour Font Awesome / Google Fonts (règle d'or skill)
|
|
1436
|
+
await page.waitForTimeout(2000);
|
|
1437
|
+
|
|
1438
|
+
const element = await page.$(info.selector);
|
|
1439
|
+
if (!element) {
|
|
1440
|
+
result = {
|
|
1441
|
+
success: false,
|
|
1442
|
+
error: `Élément ${info.selector} introuvable dans le DOM (caché par CSS, parent invisible, ou non rendu côté front)`,
|
|
1443
|
+
url: info.url,
|
|
1444
|
+
selector: info.selector,
|
|
1445
|
+
hint: "Vérifie que la page est publiée et que l'élément n'a pas _hidden ou _display: none.",
|
|
1446
|
+
};
|
|
1447
|
+
await page.close();
|
|
1448
|
+
break;
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
await element.scrollIntoViewIfNeeded();
|
|
1452
|
+
await page.waitForTimeout(400);
|
|
1453
|
+
|
|
1454
|
+
// Computed style + dimensions
|
|
1455
|
+
computed = await element.evaluate(el => {
|
|
1456
|
+
const cs = getComputedStyle(el);
|
|
1457
|
+
const rect = el.getBoundingClientRect();
|
|
1458
|
+
return {
|
|
1459
|
+
display: cs.display,
|
|
1460
|
+
'flex-direction': cs.flexDirection,
|
|
1461
|
+
'justify-content': cs.justifyContent,
|
|
1462
|
+
'align-items': cs.alignItems,
|
|
1463
|
+
gap: cs.gap,
|
|
1464
|
+
'column-gap': cs.columnGap,
|
|
1465
|
+
'row-gap': cs.rowGap,
|
|
1466
|
+
width: Math.round(rect.width) + 'px',
|
|
1467
|
+
height: Math.round(rect.height) + 'px',
|
|
1468
|
+
'max-width': cs.maxWidth,
|
|
1469
|
+
'padding-top': cs.paddingTop,
|
|
1470
|
+
'padding-right': cs.paddingRight,
|
|
1471
|
+
'padding-bottom': cs.paddingBottom,
|
|
1472
|
+
'padding-left': cs.paddingLeft,
|
|
1473
|
+
'margin-top': cs.marginTop,
|
|
1474
|
+
'margin-right': cs.marginRight,
|
|
1475
|
+
'margin-bottom': cs.marginBottom,
|
|
1476
|
+
'margin-left': cs.marginLeft,
|
|
1477
|
+
'background-color': cs.backgroundColor,
|
|
1478
|
+
'font-size': cs.fontSize,
|
|
1479
|
+
'font-family': cs.fontFamily,
|
|
1480
|
+
'font-weight': cs.fontWeight,
|
|
1481
|
+
'line-height': cs.lineHeight,
|
|
1482
|
+
color: cs.color,
|
|
1483
|
+
'text-align': cs.textAlign,
|
|
1484
|
+
'border-top-left-radius': cs.borderTopLeftRadius,
|
|
1485
|
+
'border-top-right-radius': cs.borderTopRightRadius,
|
|
1486
|
+
'border-bottom-right-radius': cs.borderBottomRightRadius,
|
|
1487
|
+
'border-bottom-left-radius': cs.borderBottomLeftRadius,
|
|
1488
|
+
visibility: cs.visibility,
|
|
1489
|
+
opacity: cs.opacity,
|
|
1490
|
+
childrenInDom: el.children.length,
|
|
1491
|
+
isVisible: rect.width > 0 && rect.height > 0 && cs.visibility !== 'hidden' && cs.opacity !== '0',
|
|
1492
|
+
hasOverflowX: el.scrollWidth > el.clientWidth,
|
|
1493
|
+
};
|
|
1494
|
+
});
|
|
1495
|
+
|
|
1496
|
+
// Fonts loaded
|
|
1497
|
+
loadedFonts = await page.evaluate(() => {
|
|
1498
|
+
const set = new Set();
|
|
1499
|
+
document.fonts.forEach(f => { if (f.status === 'loaded') set.add(f.family); });
|
|
1500
|
+
return Array.from(set);
|
|
1501
|
+
});
|
|
1502
|
+
|
|
1503
|
+
// Build checks
|
|
1504
|
+
const checks = [];
|
|
1505
|
+
checks.push({ ok: true, label: `Élément trouvé dans le DOM (${info.selector})` });
|
|
1506
|
+
checks.push({
|
|
1507
|
+
ok: computed.isVisible,
|
|
1508
|
+
label: `Élément visible (${computed.width} × ${computed.height})`,
|
|
1509
|
+
...(computed.isVisible ? {} : { hint: "Largeur ou hauteur à 0 — vérifie les enfants ou le padding du parent" })
|
|
1510
|
+
});
|
|
1511
|
+
|
|
1512
|
+
// childrenCount expected vs réel
|
|
1513
|
+
if (typeof info.childrenCount === 'number') {
|
|
1514
|
+
const ok = info.childrenCount === computed.childrenInDom;
|
|
1515
|
+
checks.push({
|
|
1516
|
+
ok,
|
|
1517
|
+
label: `${info.childrenCount} enfant(s) attendu(s) → ${computed.childrenInDom} dans le DOM`,
|
|
1518
|
+
...(ok ? {} : { expected: info.childrenCount, got: computed.childrenInDom })
|
|
1519
|
+
});
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
// Compare chaque expected
|
|
1523
|
+
const expected = info.expected || {};
|
|
1524
|
+
for (const [prop, expectedVal] of Object.entries(expected)) {
|
|
1525
|
+
const got = computed[prop];
|
|
1526
|
+
if (got === undefined) continue;
|
|
1527
|
+
const ok = normaliseCssValue(got) === normaliseCssValue(expectedVal);
|
|
1528
|
+
const check = { ok, label: `${prop} = ${expectedVal}` };
|
|
1529
|
+
if (!ok) {
|
|
1530
|
+
check.expected = expectedVal;
|
|
1531
|
+
check.got = got;
|
|
1532
|
+
const hint = generateHint(prop, expectedVal, got);
|
|
1533
|
+
if (hint) check.hint = hint;
|
|
1534
|
+
}
|
|
1535
|
+
checks.push(check);
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
// Console errors
|
|
1539
|
+
if (consoleErrors.length > 0) {
|
|
1540
|
+
checks.push({
|
|
1541
|
+
ok: false,
|
|
1542
|
+
label: `${consoleErrors.length} erreur(s) console`,
|
|
1543
|
+
got: consoleErrors.slice(0, 3),
|
|
1544
|
+
hint: "Vérifie les ressources 404 (font, image, script)",
|
|
1545
|
+
});
|
|
1546
|
+
} else {
|
|
1547
|
+
checks.push({ ok: true, label: "Aucune erreur console" });
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
// Overflow horizontal
|
|
1551
|
+
if (computed.hasOverflowX) {
|
|
1552
|
+
checks.push({
|
|
1553
|
+
ok: false,
|
|
1554
|
+
label: "Débordement horizontal détecté",
|
|
1555
|
+
hint: "Un enfant dépasse la largeur du conteneur — vérifie les _widthMax et white-space",
|
|
1556
|
+
});
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
// Screenshot crop (sur l'élément)
|
|
1560
|
+
try {
|
|
1561
|
+
const buf = await element.screenshot({ type: 'png' });
|
|
1562
|
+
screenshotBase64 = buf.toString('base64');
|
|
1563
|
+
} catch (e) {
|
|
1564
|
+
// Si element invisible/hors viewport, fallback fullpage
|
|
1565
|
+
const buf = await page.screenshot({ type: 'png', fullPage: false });
|
|
1566
|
+
screenshotBase64 = buf.toString('base64');
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
const okCount = checks.filter(c => c.ok).length;
|
|
1570
|
+
report = { score: `${okCount}/${checks.length}`, checks };
|
|
1571
|
+
|
|
1572
|
+
result = {
|
|
1573
|
+
success: true,
|
|
1574
|
+
url: info.url,
|
|
1575
|
+
urlWithAnchor: info.urlWithAnchor,
|
|
1576
|
+
selector: info.selector,
|
|
1577
|
+
name: info.name,
|
|
1578
|
+
label: info.label,
|
|
1579
|
+
viewport,
|
|
1580
|
+
report,
|
|
1581
|
+
computed,
|
|
1582
|
+
loadedFonts,
|
|
1583
|
+
};
|
|
1584
|
+
} finally {
|
|
1585
|
+
if (page && !page.isClosed()) {
|
|
1586
|
+
await page.close().catch(() => {});
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
// Réponse spéciale : ajouter l'image au content MCP pour que Claude la voie
|
|
1591
|
+
const responseContent = [
|
|
1592
|
+
{ type: "text", text: JSON.stringify(result, null, 2) },
|
|
1593
|
+
];
|
|
1594
|
+
if (screenshotBase64) {
|
|
1595
|
+
responseContent.push({
|
|
1596
|
+
type: "image",
|
|
1597
|
+
data: screenshotBase64,
|
|
1598
|
+
mimeType: "image/png",
|
|
1599
|
+
});
|
|
1600
|
+
}
|
|
1601
|
+
return { content: responseContent };
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
// ===== v3.6.0 — FEEDBACK SYSTEM =====
|
|
1605
|
+
case "report_missing_feature":
|
|
1606
|
+
result = await callWordPressAPI("/report-missing-feature", "POST", {
|
|
1607
|
+
title: args.title,
|
|
1608
|
+
bricksFeature: args.bricksFeature,
|
|
1609
|
+
bricksDocUrl: args.bricksDocUrl,
|
|
1610
|
+
whatItShouldDo: args.whatItShouldDo,
|
|
1611
|
+
whatITried: args.whatITried,
|
|
1612
|
+
proposedTool: args.proposedTool,
|
|
1613
|
+
bricksVersion: args.bricksVersion,
|
|
1614
|
+
context: args.context,
|
|
1615
|
+
});
|
|
1616
|
+
break;
|
|
1617
|
+
|
|
1618
|
+
case "list_missing_features":
|
|
1619
|
+
result = await callWordPressAPI("/list-missing-features" + (args.status ? `?status=${encodeURIComponent(args.status)}` : ""), "GET");
|
|
1620
|
+
break;
|
|
1621
|
+
|
|
1622
|
+
case "resolve_missing_feature":
|
|
1623
|
+
result = await callWordPressAPI("/resolve-missing-feature", "POST", {
|
|
1624
|
+
id: args.id,
|
|
1625
|
+
resolutionNote: args.resolutionNote,
|
|
1626
|
+
});
|
|
1627
|
+
break;
|
|
1628
|
+
|
|
1629
|
+
case "upload_media_batch":
|
|
1630
|
+
result = await callWordPressAPI("/upload-media-batch", "POST", {
|
|
1631
|
+
items: args.items,
|
|
1632
|
+
});
|
|
1633
|
+
break;
|
|
1634
|
+
|
|
1198
1635
|
default:
|
|
1199
1636
|
console.error(`[LOG] Tool inconnu: ${name}`);
|
|
1200
1637
|
result = { error: `Tool ${name} not found` };
|