@yashwant.dharmdas/elementor-mcp 3.16.0 → 3.18.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.
Files changed (2) hide show
  1. package/dist/index.js +129 -3
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -3,6 +3,8 @@ import AjvModule from "ajv";
3
3
  const Ajv = AjvModule.default || AjvModule;
4
4
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
5
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
7
+ import { createServer } from "node:http";
6
8
  import { z } from "zod";
7
9
  import axios from "axios";
8
10
  import fs from "fs";
@@ -4005,6 +4007,77 @@ function createMcpServer(sites) {
4005
4007
  return { content: [{ type: "text", text: `Error setting button: ${error.response?.data?.message || error.message}` }], isError: true };
4006
4008
  }
4007
4009
  });
4010
+ // ── set-css-filters ───────────────────────────────────────────────────────
4011
+ server.tool("set-css-filters", "Apply CSS filter effects to an image widget (or any widget via the Advanced tab) — blur, brightness, contrast, saturation, hue rotation. Use this when a Figma layer has image adjustments applied (Saturation slider, Contrast, Exposure, etc.).\n\nFIGMA → ELEMENTOR VALUE MAPPING (caller must convert):\n • Figma Saturation -100..+100 → Elementor saturation 0..200 (formula: 100 + figma_value). A fully-desaturated Figma image (-100) → Elementor saturation=0 (B&W).\n • Figma Contrast -100..+100 → Elementor contrast 0..200.\n • Figma Exposure -100..+100 → Elementor brightness 0..200 (closest CSS-filter equivalent).\n • Figma Highlights / Shadows / Temperature / Tint → no clean CSS-filter equivalent. Surface as a TODO; do NOT improvise with brightness/contrast.\n • Figma Blur → Elementor blur in px.\n\nELEMENTOR-NATIVE RANGES (what this tool accepts directly):\n • blur: 0..30 (px, 0 = none)\n • brightness: 0..200 (100 = neutral, <100 darker, >100 brighter)\n • contrast: 0..200 (100 = neutral)\n • saturation: 0..200 (100 = neutral, 0 = B&W, 200 = oversaturated)\n • hue: 0..360 (degrees, 0 = no rotation)\n\nAutomatically sets `css_filters_css_filter='custom'` so the values take effect. Pass `reset=true` to clear all filters (returns the widget to the default 'normal' filter mode). For non-image widgets, set `apply_to='any-widget'` to use the Advanced-tab `_css_filters_*` keys instead of the image-widget Style-tab `css_filters_*` keys.", {
4012
+ page_id: z.string().describe("WordPress Page ID"),
4013
+ element_id: z.string().describe("Element ID (typically an image widget; or any widget when apply_to='any-widget')"),
4014
+ blur: z.number().optional().describe("Blur in px (0..30). 0 = no blur."),
4015
+ brightness: z.number().optional().describe("Brightness 0..200 (100 = neutral)"),
4016
+ contrast: z.number().optional().describe("Contrast 0..200 (100 = neutral)"),
4017
+ saturation: z.number().optional().describe("Saturation 0..200 (100 = neutral, 0 = B&W). For a Figma layer with Saturation=-100, pass 0 here."),
4018
+ hue: z.number().optional().describe("Hue rotation in degrees 0..360"),
4019
+ hover_blur: z.number().optional().describe("Blur on hover (px)"),
4020
+ hover_brightness: z.number().optional().describe("Brightness on hover (0..200)"),
4021
+ hover_contrast: z.number().optional().describe("Contrast on hover (0..200)"),
4022
+ hover_saturation: z.number().optional().describe("Saturation on hover (0..200)"),
4023
+ hover_hue: z.number().optional().describe("Hue on hover (degrees)"),
4024
+ apply_to: z.enum(["image-widget", "any-widget"]).optional().describe("Default 'image-widget' (Style-tab keys, css_filters_*). Use 'any-widget' for filters on a non-image widget via the Advanced tab (_css_filters_*)."),
4025
+ reset: z.boolean().optional().describe("Clear all filters and set css_filter mode back to 'normal'. All numeric params are ignored when reset=true."),
4026
+ site: siteParam,
4027
+ }, async ({ page_id, element_id, blur, brightness, contrast, saturation, hue, hover_blur, hover_brightness, hover_contrast, hover_saturation, hover_hue, apply_to, reset, site }) => {
4028
+ try {
4029
+ const { wpUrl, authHeader } = resolveSite(sites, site);
4030
+ const prefix = (apply_to ?? "image-widget") === "any-widget" ? "_css_filters_" : "css_filters_";
4031
+ const slider = (n, unit = "px") => ({ unit, size: n, sizes: [] });
4032
+ const s = {};
4033
+ if (reset === true) {
4034
+ s[`${prefix}css_filter`] = "normal";
4035
+ // Zero out values explicitly so the editor doesn't keep showing stale settings
4036
+ s[`${prefix}blur`] = slider(0);
4037
+ s[`${prefix}brightness`] = slider(100);
4038
+ s[`${prefix}contrast`] = slider(100);
4039
+ s[`${prefix}saturation`] = slider(100);
4040
+ s[`${prefix}hue`] = slider(0);
4041
+ }
4042
+ else {
4043
+ // Any filter param triggers 'custom' mode
4044
+ const anySet = blur !== undefined || brightness !== undefined || contrast !== undefined ||
4045
+ saturation !== undefined || hue !== undefined ||
4046
+ hover_blur !== undefined || hover_brightness !== undefined || hover_contrast !== undefined ||
4047
+ hover_saturation !== undefined || hover_hue !== undefined;
4048
+ if (!anySet) {
4049
+ return { content: [{ type: "text", text: "No filter parameters provided. Pass at least one of blur/brightness/contrast/saturation/hue (or hover_*) — or pass reset=true." }], isError: true };
4050
+ }
4051
+ s[`${prefix}css_filter`] = "custom";
4052
+ if (blur !== undefined)
4053
+ s[`${prefix}blur`] = slider(blur);
4054
+ if (brightness !== undefined)
4055
+ s[`${prefix}brightness`] = slider(brightness);
4056
+ if (contrast !== undefined)
4057
+ s[`${prefix}contrast`] = slider(contrast);
4058
+ if (saturation !== undefined)
4059
+ s[`${prefix}saturation`] = slider(saturation);
4060
+ if (hue !== undefined)
4061
+ s[`${prefix}hue`] = slider(hue);
4062
+ // Hover variants
4063
+ if (hover_blur !== undefined)
4064
+ s[`${prefix}hover_blur`] = slider(hover_blur);
4065
+ if (hover_brightness !== undefined)
4066
+ s[`${prefix}hover_brightness`] = slider(hover_brightness);
4067
+ if (hover_contrast !== undefined)
4068
+ s[`${prefix}hover_contrast`] = slider(hover_contrast);
4069
+ if (hover_saturation !== undefined)
4070
+ s[`${prefix}hover_saturation`] = slider(hover_saturation);
4071
+ if (hover_hue !== undefined)
4072
+ s[`${prefix}hover_hue`] = slider(hover_hue);
4073
+ }
4074
+ const result = await applyElementSettings(wpUrl, authHeader, page_id, element_id, s);
4075
+ return { content: [{ type: "text", text: JSON.stringify({ applied: s, result }, null, 2) }] };
4076
+ }
4077
+ catch (error) {
4078
+ return { content: [{ type: "text", text: `Error setting CSS filters: ${error.response?.data?.message || error.message}` }], isError: true };
4079
+ }
4080
+ });
4008
4081
  // ── validate-page-structure ───────────────────────────────────────────────
4009
4082
  server.tool("validate-page-structure", "Post-build sanity check on a page. Returns a report of structural / standards violations: header widgets in page body (should be a template), images with external URLs missing WP attachment IDs, hardcoded fonts/colors that should use kit globals, legacy _element_width on flex children, missing image alt text, suspicious min-heights. Call this AFTER building a page from Figma. If errors are found, fix them with the typed tools and re-run. If only warnings remain, surface them to the human for review.", {
4010
4083
  page_id: z.string().describe("WordPress Page ID to validate"),
@@ -4034,7 +4107,60 @@ else {
4034
4107
  console.error("No sites configured. Run first: npx elementor-mcp setup");
4035
4108
  process.exit(1);
4036
4109
  }
4037
- const server = createMcpServer(sites);
4038
- const transport = new StdioServerTransport();
4039
- await server.connect(transport);
4110
+ const useStdio = process.argv.includes("--stdio");
4111
+ if (useStdio) {
4112
+ // ── Stdio mode — for Claude Desktop ──────────────────────────────────
4113
+ const server = createMcpServer(sites);
4114
+ const transport = new StdioServerTransport();
4115
+ await server.connect(transport);
4116
+ }
4117
+ else {
4118
+ // ── HTTP mode (default) — for browser-based MCP testers ──────────────
4119
+ const port = parseInt(process.env.PORT ?? "3001");
4120
+ const mcpServer = createMcpServer(sites);
4121
+ const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
4122
+ await mcpServer.connect(transport);
4123
+ const httpServer = createServer(async (req, res) => {
4124
+ // CORS — allow browser clients and Chrome Private Network Access
4125
+ res.setHeader("Access-Control-Allow-Origin", "*");
4126
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
4127
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, mcp-session-id");
4128
+ res.setHeader("Access-Control-Allow-Private-Network", "true");
4129
+ if (req.method === "OPTIONS") {
4130
+ res.writeHead(204);
4131
+ res.end();
4132
+ return;
4133
+ }
4134
+ if (req.url === "/health") {
4135
+ res.writeHead(200, { "Content-Type": "application/json" });
4136
+ res.end(JSON.stringify({ status: "ok", sites: sites.map((s) => s.name) }));
4137
+ return;
4138
+ }
4139
+ if (req.url === "/mcp") {
4140
+ let body;
4141
+ if (req.method === "POST") {
4142
+ const chunks = [];
4143
+ await new Promise((resolve, reject) => {
4144
+ req.on("data", (c) => chunks.push(c));
4145
+ req.on("end", resolve);
4146
+ req.on("error", reject);
4147
+ });
4148
+ try {
4149
+ body = JSON.parse(Buffer.concat(chunks).toString());
4150
+ }
4151
+ catch { /* non-JSON body */ }
4152
+ }
4153
+ await transport.handleRequest(req, res, body);
4154
+ return;
4155
+ }
4156
+ res.writeHead(404);
4157
+ res.end("Not found");
4158
+ });
4159
+ httpServer.listen(port, () => {
4160
+ console.log(`Elementor MCP HTTP server listening on port ${port}`);
4161
+ console.log(`MCP endpoint: http://localhost:${port}/mcp`);
4162
+ console.log(`Health check: http://localhost:${port}/health`);
4163
+ console.log(`Sites loaded: ${sites.map((s) => s.name).join(", ")}`);
4164
+ });
4165
+ }
4040
4166
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yashwant.dharmdas/elementor-mcp",
3
- "version": "3.16.0",
3
+ "version": "3.18.0",
4
4
  "description": "MCP server for controlling Elementor via Claude — supports multiple WordPress sites",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",