@yashwant.dharmdas/elementor-mcp 3.2.4 → 3.2.6

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/dist/index.js +78 -24
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1090,28 +1090,56 @@ function createMcpServer(sites) {
1090
1090
  await page.setViewport({ width: vpWidth, height: vpHeight, deviceScaleFactor: 1 });
1091
1091
  // Navigate and wait for network to go idle
1092
1092
  await page.goto(pageUrl, { waitUntil: "networkidle2", timeout: 45_000 });
1093
- // ── Auto-scroll: trigger lazy-loaded images/content ─────────────
1093
+ // ── Auto-scroll with multi-pass stabilization ────────────────────
1094
+ // Many Elementor pages lazy-load content on scroll (images, Swiper
1095
+ // carousels, reveal animations). A single scroll pass is not enough —
1096
+ // the page can grow as content loads. We keep scrolling until the
1097
+ // total page height stops increasing between passes.
1094
1098
  if (auto_scroll !== false) {
1095
1099
  await page.evaluate(async () => {
1096
- await new Promise((resolve) => {
1097
- let totalHeight = 0;
1098
- const distance = 200;
1100
+ const getHeight = () => Math.max(document.body.scrollHeight, document.documentElement.scrollHeight, document.body.offsetHeight, document.documentElement.offsetHeight);
1101
+ const scrollOnce = () => new Promise((resolve) => {
1102
+ let y = 0;
1103
+ const step = 300;
1099
1104
  const timer = setInterval(() => {
1100
- const scrollHeight = document.body.scrollHeight;
1101
- window.scrollBy(0, distance);
1102
- totalHeight += distance;
1103
- if (totalHeight >= scrollHeight) {
1105
+ const h = getHeight();
1106
+ window.scrollBy(0, step);
1107
+ y += step;
1108
+ if (y >= h) {
1104
1109
  clearInterval(timer);
1105
1110
  resolve();
1106
1111
  }
1107
- }, 80);
1112
+ }, 60);
1108
1113
  });
1114
+ // Repeat until height stabilizes (max 4 passes)
1115
+ let lastHeight = 0;
1116
+ for (let i = 0; i < 4; i++) {
1117
+ window.scrollTo(0, 0);
1118
+ await new Promise(r => setTimeout(r, 150));
1119
+ await scrollOnce();
1120
+ // Wait at the bottom for lazy content to trigger
1121
+ await new Promise(r => setTimeout(r, 800));
1122
+ const h = getHeight();
1123
+ if (h === lastHeight)
1124
+ break;
1125
+ lastHeight = h;
1126
+ }
1127
+ // Back to top for the capture
1128
+ window.scrollTo(0, 0);
1109
1129
  });
1110
- // Scroll back to top for the capture
1111
- await page.evaluate(() => window.scrollTo(0, 0));
1112
1130
  }
1113
- // ── Wait for all <img> tags to finish loading ──────────────────
1131
+ // ── Force-load all <img> tags (including data-src / loading=lazy)
1114
1132
  await page.evaluate(async () => {
1133
+ // Strip loading="lazy" so the browser loads everything eagerly
1134
+ document.querySelectorAll('img[loading="lazy"]').forEach((img) => {
1135
+ img.setAttribute("loading", "eager");
1136
+ });
1137
+ // Resolve data-src → src for libraries that defer loading
1138
+ document.querySelectorAll("img[data-src]").forEach((img) => {
1139
+ const ds = img.getAttribute("data-src");
1140
+ if (ds && !img.getAttribute("src"))
1141
+ img.setAttribute("src", ds);
1142
+ });
1115
1143
  const images = Array.from(document.querySelectorAll("img"));
1116
1144
  await Promise.all(images.map((img) => {
1117
1145
  if (img.complete && img.naturalWidth > 0)
@@ -1119,39 +1147,65 @@ function createMcpServer(sites) {
1119
1147
  return new Promise((resolve) => {
1120
1148
  img.addEventListener("load", () => resolve(), { once: true });
1121
1149
  img.addEventListener("error", () => resolve(), { once: true });
1122
- // Safety timeout per image
1123
1150
  setTimeout(() => resolve(), 5000);
1124
1151
  });
1125
1152
  }));
1126
1153
  });
1127
- // Extra settle time for fonts, animations, videos
1128
- await new Promise(resolve => setTimeout(resolve, wait ?? 2000));
1154
+ // Extra settle time for fonts, CSS animations, video posters
1155
+ await new Promise(resolve => setTimeout(resolve, wait ?? 2500));
1129
1156
  const fmt = format ?? "jpeg";
1130
1157
  const baseOptions = { type: fmt, fullPage: !max_height && (full_page ?? true) };
1131
1158
  if (max_height)
1132
1159
  baseOptions.clip = { x: 0, y: 0, width: vpWidth, height: vpHeight };
1133
- // ── Adaptive quality: always fit under the MCP 1MB tool-result limit ──
1160
+ // ── Adaptive capture: always fit under the MCP 1MB tool-result limit ──
1134
1161
  // (base64 overhead ~33%, so raw buffer must be ≤ ~780KB)
1135
1162
  const PREVIEW_LIMIT = 750 * 1024;
1136
- let buf;
1163
+ let buf = Buffer.alloc(0);
1137
1164
  let usedQuality = undefined;
1165
+ let usedScale = 1;
1166
+ const takeOne = async (q, scale) => {
1167
+ if (scale !== 1) {
1168
+ // Re-apply viewport with scale factor to shrink pixel dimensions
1169
+ await page.setViewport({ width: Math.round(vpWidth * scale), height: Math.round(vpHeight * scale), deviceScaleFactor: 1 });
1170
+ // Allow layout to reflow
1171
+ await new Promise(r => setTimeout(r, 500));
1172
+ }
1173
+ const opts = { ...baseOptions };
1174
+ if (q !== undefined)
1175
+ opts.quality = q;
1176
+ const raw = await page.screenshot(opts);
1177
+ return Buffer.isBuffer(raw) ? raw : Buffer.from(raw);
1178
+ };
1138
1179
  if (fmt === "jpeg") {
1139
- // Try user-requested quality first; fall back progressively lower until it fits.
1180
+ // Stage 1: try full resolution, progressive quality
1140
1181
  const q0 = quality ?? 82;
1141
1182
  const qualitySteps = Array.from(new Set([q0, 70, 55, 40, 25, 15])).filter(q => q <= q0);
1142
1183
  for (const q of qualitySteps) {
1143
- const raw = await page.screenshot({ ...baseOptions, quality: q });
1144
- buf = Buffer.isBuffer(raw) ? raw : Buffer.from(raw);
1184
+ buf = await takeOne(q, 1);
1145
1185
  usedQuality = q;
1146
1186
  if (buf.length <= PREVIEW_LIMIT)
1147
1187
  break;
1148
1188
  }
1189
+ // Stage 2: if still too big, shrink viewport width progressively
1190
+ const scaleSteps = [0.7, 0.5, 0.35, 0.25];
1191
+ for (const s of scaleSteps) {
1192
+ if (buf.length <= PREVIEW_LIMIT)
1193
+ break;
1194
+ buf = await takeOne(40, s);
1195
+ usedQuality = 40;
1196
+ usedScale = s;
1197
+ if (buf.length <= PREVIEW_LIMIT)
1198
+ break;
1199
+ // Inner quality drop at this scale
1200
+ buf = await takeOne(20, s);
1201
+ usedQuality = 20;
1202
+ if (buf.length <= PREVIEW_LIMIT)
1203
+ break;
1204
+ }
1149
1205
  }
1150
1206
  else {
1151
- const raw = await page.screenshot(baseOptions);
1152
- buf = Buffer.isBuffer(raw) ? raw : Buffer.from(raw);
1207
+ buf = await takeOne(undefined, 1);
1153
1208
  }
1154
- buf = buf;
1155
1209
  // ── 5. Save to disk ─────────────────────────────────────────────
1156
1210
  const ext = fmt === "jpeg" ? "jpg" : "png";
1157
1211
  const idPart = page_id ?? (pageUrl.replace(/https?:\/\//, "").replace(/[^a-z0-9]/gi, "_").slice(0, 40));
@@ -1161,7 +1215,7 @@ function createMcpServer(sites) {
1161
1215
  const sizeKB = Math.round(buf.length / 1024);
1162
1216
  const mimeType = `image/${fmt}`;
1163
1217
  const qualityNote = usedQuality !== undefined && usedQuality !== (quality ?? 82)
1164
- ? ` | Auto-downscaled to quality ${usedQuality} to fit MCP limit`
1218
+ ? ` | Auto-downscaled to quality ${usedQuality}${usedScale !== 1 ? ` @ ${Math.round(usedScale * 100)}% width` : ""} to fit MCP limit`
1165
1219
  : "";
1166
1220
  // ── 6. Always return the image so Claude can analyse it ──────
1167
1221
  const content = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yashwant.dharmdas/elementor-mcp",
3
- "version": "3.2.4",
3
+ "version": "3.2.6",
4
4
  "description": "MCP server for controlling Elementor via Claude — supports multiple WordPress sites",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",