@yashwant.dharmdas/elementor-mcp 3.3.0 → 3.4.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 +156 -0
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1782,6 +1782,162 @@ function createMcpServer(sites) {
1782
1782
  return { content: [{ type: "text", text: `Error updating video URL: ${error.response?.data?.message || error.message}` }], isError: true };
1783
1783
  }
1784
1784
  });
1785
+ // ── Group 21: WordPress Menu ─────────────────────────────────────────────────
1786
+ server.tool("update-wordpress-menu", "Read and update a WordPress navigation menu — reorder items, add items, remove items, or disable links. The Elementor Nav Menu widget order is controlled here, not in Elementor.", {
1787
+ menu_name: z.string().describe("WordPress menu name (e.g. 'Primary Menu', 'Footer Menu') or numeric menu ID"),
1788
+ action: z.enum(["get", "reorder", "disable_link", "enable_link", "remove_item", "add_item"]).describe("Action to perform: 'get' = read current menu, 'reorder' = set item order, 'disable_link' = make item non-clickable (#), 'enable_link' = restore item URL, 'remove_item' = delete a menu item, 'add_item' = append a new item"),
1789
+ item_order: z.array(z.string()).optional().describe("Required for 'reorder'. Array of item titles in desired display order."),
1790
+ item_title: z.string().optional().describe("Required for disable_link / enable_link / remove_item. The menu item title to target."),
1791
+ item_url: z.string().optional().describe("Required for add_item. The URL of the new menu item."),
1792
+ item_parent: z.string().optional().describe("For add_item — title of the parent item (for dropdown sub-items). Omit for top-level."),
1793
+ site: siteParam,
1794
+ }, async ({ menu_name, action, item_order, item_title, item_url, item_parent, site }) => {
1795
+ try {
1796
+ const { wpUrl, authHeader } = resolveSite(sites, site);
1797
+ const body = { menu_name, action };
1798
+ if (item_order)
1799
+ body.item_order = item_order;
1800
+ if (item_title)
1801
+ body.item_title = item_title;
1802
+ if (item_url)
1803
+ body.item_url = item_url;
1804
+ if (item_parent)
1805
+ body.item_parent = item_parent;
1806
+ const r = await axios.post(`${wpUrl}/wp-json/erc/v1/site/menu`, body, { headers: { Authorization: authHeader, "Content-Type": "application/json" } });
1807
+ return { content: [{ type: "text", text: JSON.stringify(r.data, null, 2) }] };
1808
+ }
1809
+ catch (error) {
1810
+ return { content: [{ type: "text", text: `Error updating WordPress menu: ${error.response?.data?.message || error.message}` }], isError: true };
1811
+ }
1812
+ });
1813
+ // ── Group 22: Rank Math SEO ───────────────────────────────────────────────────
1814
+ server.tool("update-rank-math-seo", "Update Rank Math SEO settings for a page — meta title, meta description, OG title, OG description, and social share image. Writes directly to Rank Math post meta. Use the hero section image as the og_image_url as per standard procedure.", {
1815
+ page_id: z.string().describe("WordPress Page ID"),
1816
+ meta_title: z.string().optional().describe("SEO meta title shown in search results and browser tab"),
1817
+ meta_description: z.string().optional().describe("SEO meta description (max 160 characters recommended)"),
1818
+ og_title: z.string().optional().describe("Open Graph / social share title"),
1819
+ og_description: z.string().optional().describe("Open Graph / social share description"),
1820
+ og_image_url: z.string().optional().describe("Full URL of the social share image (use hero section image as standard)"),
1821
+ og_image_id: z.number().optional().describe("WordPress media attachment ID of the OG image (preferred over URL if available)"),
1822
+ site: siteParam,
1823
+ }, async ({ page_id, meta_title, meta_description, og_title, og_description, og_image_url, og_image_id, site }) => {
1824
+ try {
1825
+ const { wpUrl, authHeader } = resolveSite(sites, site);
1826
+ // Rank Math stores its data in specific post meta keys:
1827
+ // rank_math_title, rank_math_description, rank_math_og_title,
1828
+ // rank_math_og_description, rank_math_facebook_image,
1829
+ // rank_math_facebook_image_id
1830
+ const meta = {};
1831
+ if (meta_title)
1832
+ meta.rank_math_title = meta_title;
1833
+ if (meta_description)
1834
+ meta.rank_math_description = meta_description;
1835
+ if (og_title)
1836
+ meta.rank_math_og_title = og_title;
1837
+ if (og_description)
1838
+ meta.rank_math_og_description = og_description;
1839
+ if (og_image_url)
1840
+ meta.rank_math_facebook_image = og_image_url;
1841
+ if (og_image_id)
1842
+ meta.rank_math_facebook_image_id = og_image_id;
1843
+ const r = await axios.post(`${wpUrl}/wp-json/erc/v1/pages/${page_id}/post-meta`, { meta }, { headers: { Authorization: authHeader, "Content-Type": "application/json" } });
1844
+ return { content: [{ type: "text", text: JSON.stringify(r.data, null, 2) }] };
1845
+ }
1846
+ catch (error) {
1847
+ return { content: [{ type: "text", text: `Error updating Rank Math SEO: ${error.response?.data?.message || error.message}` }], isError: true };
1848
+ }
1849
+ });
1850
+ // ── Group 23: Element Order Per Device ───────────────────────────────────────
1851
+ server.tool("set-element-order-per-device", "Set the CSS display order of an Elementor element per breakpoint — desktop, tablet, and/or mobile. Changes visual stacking order without altering the DOM. Use to move images above text on mobile (order 1) while keeping them below on desktop (order 2).", {
1852
+ page_id: z.string().describe("WordPress Page ID"),
1853
+ element_id: z.string().describe("Elementor element ID"),
1854
+ order_desktop: z.number().optional().describe("CSS order value for desktop (e.g. 1 = first, 2 = second)"),
1855
+ order_tablet: z.number().optional().describe("CSS order value for tablet"),
1856
+ order_mobile: z.number().optional().describe("CSS order value for mobile"),
1857
+ site: siteParam,
1858
+ }, async ({ page_id, element_id, order_desktop, order_tablet, order_mobile, site }) => {
1859
+ try {
1860
+ const { wpUrl, authHeader } = resolveSite(sites, site);
1861
+ const getRes = await axios.get(`${wpUrl}/wp-json/erc/v1/pages/${page_id}/elements/${element_id}`, { headers: { Authorization: authHeader } });
1862
+ const el = getRes.data;
1863
+ const orderSettings = {};
1864
+ if (order_desktop !== undefined)
1865
+ orderSettings._element_order = order_desktop;
1866
+ if (order_tablet !== undefined)
1867
+ orderSettings._element_order_tablet = order_tablet;
1868
+ if (order_mobile !== undefined)
1869
+ orderSettings._element_order_mobile = order_mobile;
1870
+ const merged = { ...el, settings: { ...(el.settings || {}), ...orderSettings } };
1871
+ const postRes = await axios.post(`${wpUrl}/wp-json/erc/v1/pages/${page_id}/elements/${element_id}`, merged, { headers: { Authorization: authHeader, "Content-Type": "application/json" } });
1872
+ return { content: [{ type: "text", text: JSON.stringify({ order_applied: orderSettings, result: postRes.data }, null, 2) }] };
1873
+ }
1874
+ catch (error) {
1875
+ return { content: [{ type: "text", text: `Error setting element order: ${error.response?.data?.message || error.message}` }], isError: true };
1876
+ }
1877
+ });
1878
+ // ── Group 24: Element Z-Index ─────────────────────────────────────────────────
1879
+ server.tool("set-element-z-index", "Set the Z-index value for a specific Elementor element. Use to fix images showing over the header, overlapping sections, or elements bleeding through other elements. Can set an absolute value or relative to another element (above/below).", {
1880
+ page_id: z.string().describe("WordPress Page ID"),
1881
+ element_id: z.string().describe("Elementor element ID to update"),
1882
+ z_index: z.number().optional().describe("Absolute Z-index value (e.g. 1, 10, 100). Use this OR reference_element_id — not both."),
1883
+ reference_element_id: z.string().optional().describe("Element ID to compare against. Use with 'position' to set z-index relative to another element."),
1884
+ position: z.enum(["above", "below"]).optional().describe("'above' = z-index of reference + 1, 'below' = z-index of reference - 1"),
1885
+ site: siteParam,
1886
+ }, async ({ page_id, element_id, z_index, reference_element_id, position, site }) => {
1887
+ try {
1888
+ const { wpUrl, authHeader } = resolveSite(sites, site);
1889
+ let finalZIndex = z_index;
1890
+ // If relative mode: fetch reference element's current z-index
1891
+ if (reference_element_id && position) {
1892
+ const refRes = await axios.get(`${wpUrl}/wp-json/erc/v1/pages/${page_id}/elements/${reference_element_id}`, { headers: { Authorization: authHeader } });
1893
+ const refZ = parseInt(refRes.data.settings?._z_index || "0", 10);
1894
+ finalZIndex = position === "above" ? refZ + 1 : Math.max(0, refZ - 1);
1895
+ }
1896
+ if (finalZIndex === undefined) {
1897
+ return { content: [{ type: "text", text: "Provide either z_index or reference_element_id + position." }], isError: true };
1898
+ }
1899
+ const getRes = await axios.get(`${wpUrl}/wp-json/erc/v1/pages/${page_id}/elements/${element_id}`, { headers: { Authorization: authHeader } });
1900
+ const el = getRes.data;
1901
+ const merged = { ...el, settings: { ...(el.settings || {}), _z_index: finalZIndex } };
1902
+ const postRes = await axios.post(`${wpUrl}/wp-json/erc/v1/pages/${page_id}/elements/${element_id}`, merged, { headers: { Authorization: authHeader, "Content-Type": "application/json" } });
1903
+ return { content: [{ type: "text", text: JSON.stringify({ z_index_set: finalZIndex, result: postRes.data }, null, 2) }] };
1904
+ }
1905
+ catch (error) {
1906
+ return { content: [{ type: "text", text: `Error setting Z-index: ${error.response?.data?.message || error.message}` }], isError: true };
1907
+ }
1908
+ });
1909
+ // ── Group 25: Video Overlay ───────────────────────────────────────────────────
1910
+ server.tool("update-video-overlay", "Set the image overlay on an Elementor video widget so the video appears to load immediately on page load. The overlay image (typically the video thumbnail or hero image) is shown until the user clicks play. This is the correct fix for 'video not loading immediately' QA issues — not changing the video URL.", {
1911
+ page_id: z.string().describe("WordPress Page ID containing the video widget"),
1912
+ widget_id: z.string().describe("Elementor element ID of the video widget"),
1913
+ overlay_image_url: z.string().describe("Full URL of the overlay/thumbnail image (use the video thumbnail or hero section image)"),
1914
+ overlay_image_id: z.number().optional().describe("WordPress media attachment ID of the overlay image (preferred over URL if available)"),
1915
+ show_play_icon: z.boolean().optional().describe("Show a play button icon over the overlay image (default: true)"),
1916
+ site: siteParam,
1917
+ }, async ({ page_id, widget_id, overlay_image_url, overlay_image_id, show_play_icon, site }) => {
1918
+ try {
1919
+ const { wpUrl, authHeader } = resolveSite(sites, site);
1920
+ const getRes = await axios.get(`${wpUrl}/wp-json/erc/v1/pages/${page_id}/elements/${widget_id}`, { headers: { Authorization: authHeader } });
1921
+ const el = getRes.data;
1922
+ const overlaySettings = {
1923
+ show_image_overlay: "yes",
1924
+ image_overlay: {
1925
+ url: overlay_image_url,
1926
+ id: overlay_image_id ?? 0,
1927
+ },
1928
+ show_play_icon: (show_play_icon ?? true) ? "yes" : "no",
1929
+ lightbox: "no", // Ensure video plays inline, not in a lightbox
1930
+ };
1931
+ const merged = { ...el, settings: { ...(el.settings || {}), ...overlaySettings } };
1932
+ const postRes = await axios.post(`${wpUrl}/wp-json/erc/v1/pages/${page_id}/elements/${widget_id}`, merged, { headers: { Authorization: authHeader, "Content-Type": "application/json" } });
1933
+ // Clear page cache so the updated overlay loads immediately
1934
+ await axios.post(`${wpUrl}/wp-json/erc/v1/site/clear-cache`, {}, { headers: { Authorization: authHeader }, params: { post_id: page_id } });
1935
+ return { content: [{ type: "text", text: JSON.stringify({ overlay_image_url, result: postRes.data }, null, 2) }] };
1936
+ }
1937
+ catch (error) {
1938
+ return { content: [{ type: "text", text: `Error updating video overlay: ${error.response?.data?.message || error.message}` }], isError: true };
1939
+ }
1940
+ });
1785
1941
  return server;
1786
1942
  }
1787
1943
  // ─── Entry Point ──────────────────────────────────────────────────────────────
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yashwant.dharmdas/elementor-mcp",
3
- "version": "3.3.0",
3
+ "version": "3.4.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",