@yashwant.dharmdas/elementor-mcp 3.2.0 → 3.2.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 (2) hide show
  1. package/dist/index.js +98 -39
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -993,29 +993,31 @@ function createMcpServer(sites) {
993
993
  }
994
994
  });
995
995
  // ── Group 9: Screenshot ──────────────────────────────────────────────────────
996
- server.tool("screenshot-page", "Take a full-page screenshot of a published WordPress page using the system-installed Google Chrome browser. Returns the screenshot as an image so you can visually review design, layout, and content. Requires the page to be publicly accessible (status: publish).", {
996
+ server.tool("screenshot-page", "Take a screenshot of a published WordPress page using system Chrome. Saves the file to disk and returns both a preview image and the saved file path. Pass the file_path to Basecamp MCP's upload_attachment tool to attach it — never relay the raw image data yourself.", {
997
997
  page_id: z.string().describe("WordPress Page ID"),
998
- full_page: z.boolean().optional().describe("Capture the full scrollable page height (default: true)"),
998
+ full_page: z.boolean().optional().describe("Capture full scrollable page (default: true)"),
999
999
  width: z.number().optional().describe("Viewport width in pixels (default: 1440)"),
1000
+ format: z.enum(["jpeg", "png"]).optional().describe("Image format — jpeg is much smaller and recommended (default: jpeg)"),
1001
+ quality: z.number().min(1).max(100).optional().describe("JPEG quality 1–100 (default: 82). Lower = smaller file size."),
1002
+ max_height: z.number().optional().describe("Cap the captured height in pixels (e.g. 4000). Useful for very long pages to stay under size limits."),
1003
+ wait: z.number().optional().describe("Extra milliseconds to wait after page load + auto-scroll before capturing (default: 2000). Increase for heavy animations/videos."),
1004
+ auto_scroll: z.boolean().optional().describe("Scroll through the page to trigger lazy-loaded images and content (default: true). Disable only for pages with infinite scroll."),
1000
1005
  site: siteParam,
1001
- }, async ({ page_id, full_page, width, site }) => {
1006
+ }, async ({ page_id, full_page, width, format, quality, max_height, wait, auto_scroll, site }) => {
1002
1007
  try {
1003
1008
  const { wpUrl, authHeader } = resolveSite(sites, site);
1004
- // ── 1. Resolve the page's public URL from WP REST API ──────────────
1009
+ // ── 1. Resolve the page's public URL ─────────────────────────────
1005
1010
  const pageRes = await axios.get(`${wpUrl}/wp-json/wp/v2/pages/${page_id}`, {
1006
1011
  headers: { Authorization: authHeader },
1007
1012
  });
1008
1013
  const pageUrl = pageRes.data.link;
1009
1014
  if (!pageUrl)
1010
1015
  throw new Error("Could not resolve public URL for this page.");
1011
- // ── 2. Detect system Chrome ──────────────────────────────────────
1016
+ // ── 2. Detect system Chrome ───────────────────────────────────────
1012
1017
  const chromePaths = [
1013
- // Windows
1014
1018
  "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
1015
1019
  "C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe",
1016
- // macOS
1017
1020
  "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
1018
- // Linux
1019
1021
  "/usr/bin/google-chrome",
1020
1022
  "/usr/bin/google-chrome-stable",
1021
1023
  "/usr/bin/chromium-browser",
@@ -1029,51 +1031,108 @@ function createMcpServer(sites) {
1029
1031
  }
1030
1032
  }
1031
1033
  if (!executablePath) {
1032
- throw new Error("Google Chrome not found on this machine.\n" +
1033
- "Install Chrome from https://www.google.com/chrome/ and ensure it is in a standard location.\n" +
1034
- "Checked paths:\n" + chromePaths.map(p => ` • ${p}`).join("\n"));
1034
+ throw new Error("Google Chrome not found. Install it from https://www.google.com/chrome/\n" +
1035
+ "Checked:\n" + chromePaths.map(p => ` • ${p}`).join("\n"));
1035
1036
  }
1036
- // ── 3. Launch puppeteer-core with system Chrome ─────────────────
1037
+ // ── 3. Ensure screenshots directory exists ───────────────────────
1038
+ const screenshotsDir = path.join(CONFIG_DIR, "screenshots");
1039
+ if (!fs.existsSync(screenshotsDir))
1040
+ fs.mkdirSync(screenshotsDir, { recursive: true });
1041
+ // ── 4. Launch puppeteer-core ──────────────────────────────────────
1037
1042
  let puppeteer;
1038
1043
  try {
1039
1044
  puppeteer = await import("puppeteer-core");
1040
1045
  }
1041
1046
  catch {
1042
- throw new Error("puppeteer-core is not installed. Run: npm install puppeteer-core\n" +
1043
- "(or reinstall @yashwant.dharmdas/elementor-mcp — it is bundled as a dependency)");
1047
+ throw new Error("puppeteer-core is not installed. Reinstall @yashwant.dharmdas/elementor-mcp to get it.");
1044
1048
  }
1045
1049
  const browser = await puppeteer.launch({
1046
1050
  executablePath,
1047
1051
  headless: true,
1048
- args: [
1049
- "--no-sandbox",
1050
- "--disable-setuid-sandbox",
1051
- "--disable-dev-shm-usage",
1052
- "--disable-gpu",
1053
- ],
1052
+ args: ["--no-sandbox", "--disable-setuid-sandbox", "--disable-dev-shm-usage", "--disable-gpu"],
1054
1053
  });
1055
1054
  try {
1056
1055
  const page = await browser.newPage();
1057
- await page.setViewport({ width: width ?? 1440, height: 900, deviceScaleFactor: 1 });
1058
- await page.goto(pageUrl, { waitUntil: "networkidle2", timeout: 30_000 });
1059
- // Small pause to let JS-heavy pages finish rendering
1060
- await new Promise(resolve => setTimeout(resolve, 1000));
1061
- const screenshotBuffer = await page.screenshot({
1062
- fullPage: full_page ?? true,
1063
- type: "png",
1056
+ const vpWidth = width ?? 1440;
1057
+ const vpHeight = max_height ?? 900;
1058
+ await page.setViewport({ width: vpWidth, height: vpHeight, deviceScaleFactor: 1 });
1059
+ // Navigate and wait for network to go idle
1060
+ await page.goto(pageUrl, { waitUntil: "networkidle2", timeout: 45_000 });
1061
+ // ── Auto-scroll: trigger lazy-loaded images/content ─────────────
1062
+ if (auto_scroll !== false) {
1063
+ await page.evaluate(async () => {
1064
+ await new Promise((resolve) => {
1065
+ let totalHeight = 0;
1066
+ const distance = 200;
1067
+ const timer = setInterval(() => {
1068
+ const scrollHeight = document.body.scrollHeight;
1069
+ window.scrollBy(0, distance);
1070
+ totalHeight += distance;
1071
+ if (totalHeight >= scrollHeight) {
1072
+ clearInterval(timer);
1073
+ resolve();
1074
+ }
1075
+ }, 80);
1076
+ });
1077
+ });
1078
+ // Scroll back to top for the capture
1079
+ await page.evaluate(() => window.scrollTo(0, 0));
1080
+ }
1081
+ // ── Wait for all <img> tags to finish loading ──────────────────
1082
+ await page.evaluate(async () => {
1083
+ const images = Array.from(document.querySelectorAll("img"));
1084
+ await Promise.all(images.map((img) => {
1085
+ if (img.complete && img.naturalWidth > 0)
1086
+ return Promise.resolve();
1087
+ return new Promise((resolve) => {
1088
+ img.addEventListener("load", () => resolve(), { once: true });
1089
+ img.addEventListener("error", () => resolve(), { once: true });
1090
+ // Safety timeout per image
1091
+ setTimeout(() => resolve(), 5000);
1092
+ });
1093
+ }));
1064
1094
  });
1065
- const base64 = Buffer.isBuffer(screenshotBuffer)
1066
- ? screenshotBuffer.toString("base64")
1067
- : Buffer.from(screenshotBuffer).toString("base64");
1068
- return {
1069
- content: [
1070
- {
1071
- type: "image",
1072
- data: base64,
1073
- mimeType: "image/png",
1074
- },
1075
- ],
1076
- };
1095
+ // Extra settle time for fonts, animations, videos
1096
+ await new Promise(resolve => setTimeout(resolve, wait ?? 2000));
1097
+ const fmt = format ?? "jpeg";
1098
+ const screenshotOptions = { type: fmt, fullPage: !max_height && (full_page ?? true) };
1099
+ if (fmt === "jpeg")
1100
+ screenshotOptions.quality = quality ?? 82;
1101
+ if (max_height)
1102
+ screenshotOptions.clip = { x: 0, y: 0, width: vpWidth, height: vpHeight };
1103
+ const screenshotBuffer = await page.screenshot(screenshotOptions);
1104
+ const buf = Buffer.isBuffer(screenshotBuffer) ? screenshotBuffer : Buffer.from(screenshotBuffer);
1105
+ // ── 5. Save to disk ─────────────────────────────────────────────
1106
+ const ext = fmt === "jpeg" ? "jpg" : "png";
1107
+ const filename = `page_${page_id}_${Date.now()}.${ext}`;
1108
+ const filePath = path.join(screenshotsDir, filename);
1109
+ fs.writeFileSync(filePath, buf);
1110
+ const sizeKB = Math.round(buf.length / 1024);
1111
+ const mimeType = `image/${fmt}`;
1112
+ // ── 6. Return image preview if small enough, always return path ─
1113
+ const PREVIEW_LIMIT = 750 * 1024; // 750 KB raw → ~1 MB base64
1114
+ const content = [
1115
+ {
1116
+ type: "text",
1117
+ text: [
1118
+ `✅ Screenshot saved successfully.`,
1119
+ `📁 file_path: ${filePath}`,
1120
+ `📐 Size: ${sizeKB} KB | Format: ${ext.toUpperCase()} | URL: ${pageUrl}`,
1121
+ ``,
1122
+ `👉 To upload to Basecamp, call upload_attachment with:`,
1123
+ ` file_path = "${filePath}"`,
1124
+ ` project_id = <your Basecamp project ID>`,
1125
+ ].join("\n"),
1126
+ },
1127
+ ];
1128
+ if (buf.length <= PREVIEW_LIMIT) {
1129
+ content.unshift({
1130
+ type: "image",
1131
+ data: buf.toString("base64"),
1132
+ mimeType,
1133
+ });
1134
+ }
1135
+ return { content };
1077
1136
  }
1078
1137
  finally {
1079
1138
  await browser.close();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yashwant.dharmdas/elementor-mcp",
3
- "version": "3.2.0",
3
+ "version": "3.2.2",
4
4
  "description": "MCP server for controlling Elementor via Claude — supports multiple WordPress sites",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",