@yashwant.dharmdas/elementor-mcp 3.9.0 → 3.11.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 +175 -2
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -270,6 +270,89 @@ function createMcpServer(sites) {
270
270
  return { content: [{ type: "text", text: `Error setting element link: ${error.response?.data?.message || error.message}` }], isError: true };
271
271
  }
272
272
  });
273
+ server.tool("clone-element", "Clone an existing Elementor element (button, heading, container, etc.) with optional setting overrides and insert it at a position. **PERFECT for 'add another button like this one' tasks** — preserves all styling, classes, sizing, and structure from the source widget; you just override the content (text, link, image, etc.). 100 % server-side: no full-page download or upload — uses ~95 % fewer tokens than get-data + update-data. ALWAYS prefer this over add-widget-to-page or update-data when adding similar elements.", {
274
+ page_id: z.string().describe("WordPress Page ID"),
275
+ element_id: z.string().describe("Element ID to clone (the source — its styling and structure are copied; new IDs are auto-generated for the clone)"),
276
+ position: z.enum(["before", "after", "first_child", "last_child"]).optional().describe("Where to place the clone relative to target_id. Default: 'after'"),
277
+ target_id: z.string().optional().describe("Place clone near this element instead of the source. Default: same as element_id (places clone next to source)"),
278
+ overrides: z.string().optional().describe("JSON object of settings to override on the clone. Example: '{\"text\":\"Go to Growth99\",\"link\":{\"url\":\"https://growth99.com\",\"is_external\":\"on\"}}'. Top-level keys deep-merge for objects, replace for scalars."),
279
+ site: siteParam,
280
+ }, async ({ page_id, element_id, position, target_id, overrides, site }) => {
281
+ try {
282
+ let parsedOverrides = {};
283
+ if (overrides) {
284
+ try {
285
+ parsedOverrides = JSON.parse(overrides);
286
+ }
287
+ catch {
288
+ return { content: [{ type: "text", text: "Invalid JSON in overrides parameter." }], isError: true };
289
+ }
290
+ }
291
+ const { wpUrl, authHeader } = resolveSite(sites, site);
292
+ const body = {};
293
+ if (position)
294
+ body.position = position;
295
+ if (target_id)
296
+ body.target_id = target_id;
297
+ if (overrides)
298
+ body.overrides = parsedOverrides;
299
+ const r = await axios.post(`${wpUrl}/wp-json/erc/v1/pages/${page_id}/elements/${element_id}/clone`, body, { headers: { Authorization: authHeader, "Content-Type": "application/json" } });
300
+ return { content: [{ type: "text", text: JSON.stringify(r.data, null, 2) }] };
301
+ }
302
+ catch (error) {
303
+ return { content: [{ type: "text", text: `Error cloning element: ${error.response?.data?.message || error.message}` }], isError: true };
304
+ }
305
+ });
306
+ server.tool("insert-element", "Insert a brand-new Elementor element (custom JSON) at a specific position. Server-side — never downloads or uploads the full page JSON. Use this when there's no similar element to clone. For 'add another button like this one' use clone-element instead — it preserves styling automatically.", {
307
+ page_id: z.string().describe("WordPress Page ID"),
308
+ element: z.string().describe("JSON string of the element to insert. Schema: {elType, widgetType, settings, elements?}. ID auto-generated."),
309
+ target_id: z.string().optional().describe("Element ID to insert relative to. Omit to insert at page top/bottom."),
310
+ position: z.string().optional().describe("With target_id: 'before', 'after', 'first_child', 'last_child' (default 'after'). Without target_id: 'top' or 'bottom' (default 'bottom')."),
311
+ site: siteParam,
312
+ }, async ({ page_id, element, target_id, position, site }) => {
313
+ try {
314
+ let parsedElement;
315
+ try {
316
+ parsedElement = JSON.parse(element);
317
+ }
318
+ catch {
319
+ return { content: [{ type: "text", text: "Invalid JSON in element parameter." }], isError: true };
320
+ }
321
+ if (!parsedElement || typeof parsedElement !== "object") {
322
+ return { content: [{ type: "text", text: "element must be a JSON object." }], isError: true };
323
+ }
324
+ const { wpUrl, authHeader } = resolveSite(sites, site);
325
+ const body = { element: parsedElement };
326
+ if (target_id)
327
+ body.target_id = target_id;
328
+ if (position)
329
+ body.position = position;
330
+ const r = await axios.post(`${wpUrl}/wp-json/erc/v1/pages/${page_id}/insert-element`, body, { headers: { Authorization: authHeader, "Content-Type": "application/json" } });
331
+ return { content: [{ type: "text", text: JSON.stringify(r.data, null, 2) }] };
332
+ }
333
+ catch (error) {
334
+ return { content: [{ type: "text", text: `Error inserting element: ${error.response?.data?.message || error.message}` }], isError: true };
335
+ }
336
+ });
337
+ server.tool("move-element", "Move an existing Elementor element to a new position in the tree. Server-side — no full-page round-trip.", {
338
+ page_id: z.string().describe("WordPress Page ID"),
339
+ element_id: z.string().describe("Element ID to move"),
340
+ target_id: z.string().describe("Element ID to move next to"),
341
+ position: z.enum(["before", "after", "first_child", "last_child"]).optional().describe("Position relative to target_id. Default: 'after'"),
342
+ site: siteParam,
343
+ }, async ({ page_id, element_id, target_id, position, site }) => {
344
+ try {
345
+ const { wpUrl, authHeader } = resolveSite(sites, site);
346
+ const body = { target_id };
347
+ if (position)
348
+ body.position = position;
349
+ const r = await axios.post(`${wpUrl}/wp-json/erc/v1/pages/${page_id}/elements/${element_id}/move`, body, { headers: { Authorization: authHeader, "Content-Type": "application/json" } });
350
+ return { content: [{ type: "text", text: JSON.stringify(r.data, null, 2) }] };
351
+ }
352
+ catch (error) {
353
+ return { content: [{ type: "text", text: `Error moving element: ${error.response?.data?.message || error.message}` }], isError: true };
354
+ }
355
+ });
273
356
  server.tool("merge-element-settings", "Deep-merge settings into a specific Elementor element without replacing the full element. Use for small setting adjustments.", {
274
357
  page_id: z.string().describe("WordPress Page ID"),
275
358
  element_id: z.string().describe("Elementor Element ID"),
@@ -1008,13 +1091,14 @@ function createMcpServer(sites) {
1008
1091
  }
1009
1092
  });
1010
1093
  // ── Group 8: Page Creation & Publishing ──────────────────────────────────
1011
- server.tool("create-page", "Create a new WordPress page, optionally publish it immediately, and enable the Elementor editor. Returns the page ID and direct Elementor edit URL.", {
1094
+ server.tool("create-page", "Create a new WordPress page, optionally publish it immediately, and enable the Elementor editor. Returns the page ID and direct Elementor edit URL. Supports any WordPress post type — use post_type='astra-portfolio' for Astra portfolio items, 'post' for blog posts, etc.", {
1012
1095
  title: z.string().describe("Page title"),
1013
1096
  status: z.enum(["draft", "publish", "private", "pending"]).optional().describe("Page status — default: draft"),
1014
1097
  page_template: z.string().optional().describe("WordPress page template filename (e.g. 'elementor_canvas'). Leave blank for default."),
1015
1098
  enable_elementor: z.boolean().optional().describe("Initialize the Elementor editor on this page (default: true)"),
1099
+ post_type: z.string().optional().describe("WordPress post type. Default: 'page'. Use 'astra-portfolio' for Astra portfolio, 'post' for blog posts, or any registered custom post type."),
1016
1100
  site: siteParam,
1017
- }, async ({ title, status, page_template, enable_elementor, site }) => {
1101
+ }, async ({ title, status, page_template, enable_elementor, post_type, site }) => {
1018
1102
  try {
1019
1103
  const { wpUrl, authHeader } = resolveSite(sites, site);
1020
1104
  const body = {
@@ -1024,6 +1108,8 @@ function createMcpServer(sites) {
1024
1108
  };
1025
1109
  if (page_template)
1026
1110
  body.page_template = page_template;
1111
+ if (post_type)
1112
+ body.post_type = post_type;
1027
1113
  const r = await axios.post(`${wpUrl}/wp-json/erc/v1/pages`, body, {
1028
1114
  headers: { Authorization: authHeader, "Content-Type": "application/json" },
1029
1115
  });
@@ -1033,6 +1119,93 @@ function createMcpServer(sites) {
1033
1119
  return { content: [{ type: "text", text: `Error creating page: ${error.response?.data?.message || error.message}` }], isError: true };
1034
1120
  }
1035
1121
  });
1122
+ server.tool("import-template-to-page", "Push a local Elementor JSON file from disk directly to an existing page. The MCP server reads the file and sends it straight to WordPress — the JSON NEVER passes through Claude's context, so there is NO 25K-token limit. Use when you have generated a large Elementor JSON locally and need to push it to an existing page. File path must be accessible on this machine (where the MCP server is running).", {
1123
+ page_id: z.string().describe("WordPress Page ID to write the Elementor data to"),
1124
+ file_path: z.string().describe("Absolute path to the Elementor JSON file on disk (e.g. D:\\Project\\final-service-pages\\botox.json). Must contain a valid Elementor elements array."),
1125
+ site: siteParam,
1126
+ }, async ({ page_id, file_path, site }) => {
1127
+ try {
1128
+ const { wpUrl, authHeader } = resolveSite(sites, site);
1129
+ // Read file from local disk — bypasses Claude context entirely
1130
+ if (!fs.existsSync(file_path)) {
1131
+ return { content: [{ type: "text", text: `Error: File not found at path: ${file_path}` }], isError: true };
1132
+ }
1133
+ const raw = fs.readFileSync(file_path, "utf8");
1134
+ let elementorData;
1135
+ try {
1136
+ elementorData = JSON.parse(raw);
1137
+ }
1138
+ catch {
1139
+ return { content: [{ type: "text", text: `Error: File at ${file_path} is not valid JSON.` }], isError: true };
1140
+ }
1141
+ // Push to WordPress
1142
+ const r = await axios.put(`${wpUrl}/wp-json/erc/v1/pages/${page_id}/data`, elementorData, { headers: { Authorization: authHeader, "Content-Type": "application/json" } });
1143
+ // Clear cache
1144
+ await axios.post(`${wpUrl}/wp-json/erc/v1/site/clear-cache`, {}, { headers: { Authorization: authHeader }, params: { post_id: page_id } }).catch(() => { });
1145
+ return { content: [{ type: "text", text: JSON.stringify({ success: true, page_id, file: file_path, result: r.data }, null, 2) }] };
1146
+ }
1147
+ catch (error) {
1148
+ return { content: [{ type: "text", text: `Error importing template: ${error.response?.data?.message || error.message}` }], isError: true };
1149
+ }
1150
+ });
1151
+ server.tool("create-page-from-file", "Create a new WordPress page AND populate it with Elementor data from a local JSON file — all in one step. The MCP server reads the file from disk and sends it straight to WordPress. The JSON NEVER passes through Claude's context, so there is NO 25K-token limit. This is the correct tool for the service-pages workflow: write a script that generates Elementor JSONs locally, then call this tool for each file. Supports any post_type (astra-portfolio, page, post, etc.). Steps internally: 1) create page → get ID, 2) push Elementor data from file, 3) clear cache.", {
1152
+ title: z.string().describe("Page title"),
1153
+ file_path: z.string().describe("Absolute path to the Elementor JSON file on disk (e.g. D:\\Project\\final-service-pages\\botox.json)"),
1154
+ post_type: z.string().optional().describe("WordPress post type. Default: 'page'. Use 'astra-portfolio' for Astra portfolio items, 'post' for blog posts, or any registered custom post type."),
1155
+ status: z.enum(["draft", "publish", "private", "pending"]).optional().describe("Page status — default: publish"),
1156
+ page_template: z.string().optional().describe("WordPress page template (e.g. 'elementor_canvas'). Leave blank for default."),
1157
+ site: siteParam,
1158
+ }, async ({ title, file_path, post_type, status, page_template, site }) => {
1159
+ try {
1160
+ const { wpUrl, authHeader } = resolveSite(sites, site);
1161
+ // Step 1: Read the Elementor JSON from disk
1162
+ if (!fs.existsSync(file_path)) {
1163
+ return { content: [{ type: "text", text: `Error: File not found at path: ${file_path}` }], isError: true };
1164
+ }
1165
+ const raw = fs.readFileSync(file_path, "utf8");
1166
+ let elementorData;
1167
+ try {
1168
+ elementorData = JSON.parse(raw);
1169
+ }
1170
+ catch {
1171
+ return { content: [{ type: "text", text: `Error: File at ${file_path} is not valid JSON.` }], isError: true };
1172
+ }
1173
+ // Step 2: Create the page
1174
+ const createBody = {
1175
+ title,
1176
+ status: status ?? "publish",
1177
+ enable_elementor: true,
1178
+ };
1179
+ if (post_type)
1180
+ createBody.post_type = post_type;
1181
+ if (page_template)
1182
+ createBody.page_template = page_template;
1183
+ const createRes = await axios.post(`${wpUrl}/wp-json/erc/v1/pages`, createBody, { headers: { Authorization: authHeader, "Content-Type": "application/json" } });
1184
+ const page_id = createRes.data.id;
1185
+ // Step 3: Push Elementor data from file (never entered Claude context)
1186
+ await axios.put(`${wpUrl}/wp-json/erc/v1/pages/${page_id}/data`, elementorData, { headers: { Authorization: authHeader, "Content-Type": "application/json" } });
1187
+ // Step 4: Clear cache
1188
+ await axios.post(`${wpUrl}/wp-json/erc/v1/site/clear-cache`, {}, { headers: { Authorization: authHeader }, params: { post_id: page_id } }).catch(() => { });
1189
+ return {
1190
+ content: [{
1191
+ type: "text",
1192
+ text: JSON.stringify({
1193
+ success: true,
1194
+ page_id,
1195
+ title,
1196
+ post_type: post_type ?? "page",
1197
+ status: status ?? "publish",
1198
+ url: createRes.data.url,
1199
+ edit_url: createRes.data.elementor_edit_url,
1200
+ file_used: file_path,
1201
+ }, null, 2),
1202
+ }],
1203
+ };
1204
+ }
1205
+ catch (error) {
1206
+ return { content: [{ type: "text", text: `Error creating page from file: ${error.response?.data?.message || error.message}` }], isError: true };
1207
+ }
1208
+ });
1036
1209
  server.tool("publish-page", "Change the publish status of a WordPress page (publish, draft, private, pending, trash).", {
1037
1210
  page_id: z.string().describe("WordPress Page ID"),
1038
1211
  status: z.enum(["publish", "draft", "private", "pending", "trash"]).describe("Target status"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yashwant.dharmdas/elementor-mcp",
3
- "version": "3.9.0",
3
+ "version": "3.11.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",