@yashwant.dharmdas/elementor-mcp 3.3.0 → 3.5.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.
- package/dist/index.js +224 -17
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -849,42 +849,70 @@ function createMcpServer(sites) {
|
|
|
849
849
|
return { content: [{ type: "text", text: `Error: ${e.response?.data?.message || e.message}` }], isError: true };
|
|
850
850
|
}
|
|
851
851
|
});
|
|
852
|
-
server.tool("create-custom-code", "Create a new custom code snippet (requires Elementor Pro).", {
|
|
853
|
-
title: z.string(),
|
|
854
|
-
code: z.string(),
|
|
855
|
-
location: z.string().optional().describe("head
|
|
856
|
-
status: z.string().optional(),
|
|
852
|
+
server.tool("create-custom-code", "Create a new custom code snippet (requires Elementor Pro). Supports the full Publish Settings — set condition='entire_site' so the snippet actually applies sitewide (otherwise it has no effect even when published).", {
|
|
853
|
+
title: z.string().describe("Snippet title (e.g. 'Favicon Code')"),
|
|
854
|
+
code: z.string().describe("Raw code to inject (HTML, JS, or CSS)"),
|
|
855
|
+
location: z.string().optional().describe("Where to inject: 'head', 'body_start', 'body_end' (default: head)"),
|
|
856
|
+
status: z.string().optional().describe("Post status: 'publish' (default) or 'draft'. Use 'publish' to make the snippet active immediately."),
|
|
857
|
+
priority: z.number().optional().describe("Load priority — lower number = earlier. Default: 10"),
|
|
858
|
+
condition: z.enum(["entire_site", "specific_page", "post_type"]).optional().describe("Where the snippet applies. 'entire_site' = sitewide (most common, required for favicons/global tags). Default: entire_site"),
|
|
859
|
+
page_id: z.string().optional().describe("Required when condition='specific_page'. Apply only to this page."),
|
|
860
|
+
post_type: z.string().optional().describe("Required when condition='post_type'. WordPress post type slug (e.g. 'page', 'post', 'astra-portfolio')."),
|
|
857
861
|
site: siteParam,
|
|
858
|
-
}, async ({ title, code, location, status, site }) => {
|
|
862
|
+
}, async ({ title, code, location, status, priority, condition, page_id, post_type, site }) => {
|
|
859
863
|
try {
|
|
860
864
|
const { wpUrl, authHeader } = resolveSite(sites, site);
|
|
861
|
-
const
|
|
865
|
+
const body = {
|
|
866
|
+
title,
|
|
867
|
+
code,
|
|
868
|
+
location: location || "head",
|
|
869
|
+
status: status || "publish",
|
|
870
|
+
priority: priority ?? 10,
|
|
871
|
+
condition: condition || "entire_site",
|
|
872
|
+
};
|
|
873
|
+
if (condition === "specific_page" && page_id)
|
|
874
|
+
body.page_id = page_id;
|
|
875
|
+
if (condition === "post_type" && post_type)
|
|
876
|
+
body.post_type = post_type;
|
|
877
|
+
const r = await axios.post(`${wpUrl}/wp-json/erc/v1/site/custom-code`, body, { headers: { Authorization: authHeader, "Content-Type": "application/json" } });
|
|
862
878
|
return { content: [{ type: "text", text: JSON.stringify(r.data, null, 2) }] };
|
|
863
879
|
}
|
|
864
880
|
catch (e) {
|
|
865
881
|
return { content: [{ type: "text", text: `Error: ${e.response?.data?.message || e.message}` }], isError: true };
|
|
866
882
|
}
|
|
867
883
|
});
|
|
868
|
-
server.tool("update-custom-code", "Update a custom code snippet (requires Elementor Pro).", {
|
|
869
|
-
snippet_id: z.string(),
|
|
884
|
+
server.tool("update-custom-code", "Update a custom code snippet (requires Elementor Pro). Can also update Publish Settings — condition, priority, and status.", {
|
|
885
|
+
snippet_id: z.string().describe("ID of the custom code snippet"),
|
|
870
886
|
title: z.string().optional(),
|
|
871
887
|
code: z.string().optional(),
|
|
872
|
-
location: z.string().optional(),
|
|
873
|
-
status: z.string().optional(),
|
|
888
|
+
location: z.string().optional().describe("'head', 'body_start', or 'body_end'"),
|
|
889
|
+
status: z.string().optional().describe("'publish' or 'draft'"),
|
|
890
|
+
priority: z.number().optional().describe("Load priority (lower = earlier)"),
|
|
891
|
+
condition: z.enum(["entire_site", "specific_page", "post_type"]).optional().describe("Where the snippet applies"),
|
|
892
|
+
page_id: z.string().optional().describe("Required when condition='specific_page'"),
|
|
893
|
+
post_type: z.string().optional().describe("Required when condition='post_type'"),
|
|
874
894
|
site: siteParam,
|
|
875
|
-
}, async ({ snippet_id, title, code, location, status, site }) => {
|
|
895
|
+
}, async ({ snippet_id, title, code, location, status, priority, condition, page_id, post_type, site }) => {
|
|
876
896
|
try {
|
|
877
897
|
const { wpUrl, authHeader } = resolveSite(sites, site);
|
|
878
898
|
const body = {};
|
|
879
|
-
if (title)
|
|
899
|
+
if (title !== undefined)
|
|
880
900
|
body.title = title;
|
|
881
|
-
if (code)
|
|
901
|
+
if (code !== undefined)
|
|
882
902
|
body.code = code;
|
|
883
|
-
if (location)
|
|
903
|
+
if (location !== undefined)
|
|
884
904
|
body.location = location;
|
|
885
|
-
if (status)
|
|
905
|
+
if (status !== undefined)
|
|
886
906
|
body.status = status;
|
|
887
|
-
|
|
907
|
+
if (priority !== undefined)
|
|
908
|
+
body.priority = priority;
|
|
909
|
+
if (condition !== undefined)
|
|
910
|
+
body.condition = condition;
|
|
911
|
+
if (page_id !== undefined)
|
|
912
|
+
body.page_id = page_id;
|
|
913
|
+
if (post_type !== undefined)
|
|
914
|
+
body.post_type = post_type;
|
|
915
|
+
const r = await axios.put(`${wpUrl}/wp-json/erc/v1/site/custom-code/${snippet_id}`, body, { headers: { Authorization: authHeader, "Content-Type": "application/json" } });
|
|
888
916
|
return { content: [{ type: "text", text: JSON.stringify(r.data, null, 2) }] };
|
|
889
917
|
}
|
|
890
918
|
catch (e) {
|
|
@@ -1782,6 +1810,185 @@ function createMcpServer(sites) {
|
|
|
1782
1810
|
return { content: [{ type: "text", text: `Error updating video URL: ${error.response?.data?.message || error.message}` }], isError: true };
|
|
1783
1811
|
}
|
|
1784
1812
|
});
|
|
1813
|
+
// ── Group 21: WordPress Menu ─────────────────────────────────────────────────
|
|
1814
|
+
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.", {
|
|
1815
|
+
menu_name: z.string().describe("WordPress menu name (e.g. 'Primary Menu', 'Footer Menu') or numeric menu ID"),
|
|
1816
|
+
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"),
|
|
1817
|
+
item_order: z.array(z.string()).optional().describe("Required for 'reorder'. Array of item titles in desired display order."),
|
|
1818
|
+
item_title: z.string().optional().describe("Required for disable_link / enable_link / remove_item. The menu item title to target."),
|
|
1819
|
+
item_url: z.string().optional().describe("Required for add_item. The URL of the new menu item."),
|
|
1820
|
+
item_parent: z.string().optional().describe("For add_item — title of the parent item (for dropdown sub-items). Omit for top-level."),
|
|
1821
|
+
site: siteParam,
|
|
1822
|
+
}, async ({ menu_name, action, item_order, item_title, item_url, item_parent, site }) => {
|
|
1823
|
+
try {
|
|
1824
|
+
const { wpUrl, authHeader } = resolveSite(sites, site);
|
|
1825
|
+
const body = { menu_name, action };
|
|
1826
|
+
if (item_order)
|
|
1827
|
+
body.item_order = item_order;
|
|
1828
|
+
if (item_title)
|
|
1829
|
+
body.item_title = item_title;
|
|
1830
|
+
if (item_url)
|
|
1831
|
+
body.item_url = item_url;
|
|
1832
|
+
if (item_parent)
|
|
1833
|
+
body.item_parent = item_parent;
|
|
1834
|
+
const r = await axios.post(`${wpUrl}/wp-json/erc/v1/site/menu`, body, { headers: { Authorization: authHeader, "Content-Type": "application/json" } });
|
|
1835
|
+
return { content: [{ type: "text", text: JSON.stringify(r.data, null, 2) }] };
|
|
1836
|
+
}
|
|
1837
|
+
catch (error) {
|
|
1838
|
+
return { content: [{ type: "text", text: `Error updating WordPress menu: ${error.response?.data?.message || error.message}` }], isError: true };
|
|
1839
|
+
}
|
|
1840
|
+
});
|
|
1841
|
+
// ── Group 22: Rank Math SEO ───────────────────────────────────────────────────
|
|
1842
|
+
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.", {
|
|
1843
|
+
page_id: z.string().describe("WordPress Page ID"),
|
|
1844
|
+
meta_title: z.string().optional().describe("SEO meta title shown in search results and browser tab"),
|
|
1845
|
+
meta_description: z.string().optional().describe("SEO meta description (max 160 characters recommended)"),
|
|
1846
|
+
og_title: z.string().optional().describe("Open Graph / social share title"),
|
|
1847
|
+
og_description: z.string().optional().describe("Open Graph / social share description"),
|
|
1848
|
+
og_image_url: z.string().optional().describe("Full URL of the social share image (use hero section image as standard)"),
|
|
1849
|
+
og_image_id: z.number().optional().describe("WordPress media attachment ID of the OG image (preferred over URL if available)"),
|
|
1850
|
+
site: siteParam,
|
|
1851
|
+
}, async ({ page_id, meta_title, meta_description, og_title, og_description, og_image_url, og_image_id, site }) => {
|
|
1852
|
+
try {
|
|
1853
|
+
const { wpUrl, authHeader } = resolveSite(sites, site);
|
|
1854
|
+
// Rank Math stores its data in specific post meta keys:
|
|
1855
|
+
// rank_math_title, rank_math_description, rank_math_og_title,
|
|
1856
|
+
// rank_math_og_description, rank_math_facebook_image,
|
|
1857
|
+
// rank_math_facebook_image_id
|
|
1858
|
+
const meta = {};
|
|
1859
|
+
if (meta_title)
|
|
1860
|
+
meta.rank_math_title = meta_title;
|
|
1861
|
+
if (meta_description)
|
|
1862
|
+
meta.rank_math_description = meta_description;
|
|
1863
|
+
if (og_title)
|
|
1864
|
+
meta.rank_math_og_title = og_title;
|
|
1865
|
+
if (og_description)
|
|
1866
|
+
meta.rank_math_og_description = og_description;
|
|
1867
|
+
if (og_image_url)
|
|
1868
|
+
meta.rank_math_facebook_image = og_image_url;
|
|
1869
|
+
if (og_image_id)
|
|
1870
|
+
meta.rank_math_facebook_image_id = og_image_id;
|
|
1871
|
+
const r = await axios.post(`${wpUrl}/wp-json/erc/v1/pages/${page_id}/post-meta`, { meta }, { headers: { Authorization: authHeader, "Content-Type": "application/json" } });
|
|
1872
|
+
return { content: [{ type: "text", text: JSON.stringify(r.data, null, 2) }] };
|
|
1873
|
+
}
|
|
1874
|
+
catch (error) {
|
|
1875
|
+
return { content: [{ type: "text", text: `Error updating Rank Math SEO: ${error.response?.data?.message || error.message}` }], isError: true };
|
|
1876
|
+
}
|
|
1877
|
+
});
|
|
1878
|
+
// ── Group 23: Element Order Per Device ───────────────────────────────────────
|
|
1879
|
+
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).", {
|
|
1880
|
+
page_id: z.string().describe("WordPress Page ID"),
|
|
1881
|
+
element_id: z.string().describe("Elementor element ID"),
|
|
1882
|
+
order_desktop: z.number().optional().describe("CSS order value for desktop (e.g. 1 = first, 2 = second)"),
|
|
1883
|
+
order_tablet: z.number().optional().describe("CSS order value for tablet"),
|
|
1884
|
+
order_mobile: z.number().optional().describe("CSS order value for mobile"),
|
|
1885
|
+
site: siteParam,
|
|
1886
|
+
}, async ({ page_id, element_id, order_desktop, order_tablet, order_mobile, site }) => {
|
|
1887
|
+
try {
|
|
1888
|
+
const { wpUrl, authHeader } = resolveSite(sites, site);
|
|
1889
|
+
const getRes = await axios.get(`${wpUrl}/wp-json/erc/v1/pages/${page_id}/elements/${element_id}`, { headers: { Authorization: authHeader } });
|
|
1890
|
+
const el = getRes.data;
|
|
1891
|
+
const orderSettings = {};
|
|
1892
|
+
if (order_desktop !== undefined)
|
|
1893
|
+
orderSettings._element_order = order_desktop;
|
|
1894
|
+
if (order_tablet !== undefined)
|
|
1895
|
+
orderSettings._element_order_tablet = order_tablet;
|
|
1896
|
+
if (order_mobile !== undefined)
|
|
1897
|
+
orderSettings._element_order_mobile = order_mobile;
|
|
1898
|
+
const merged = { ...el, settings: { ...(el.settings || {}), ...orderSettings } };
|
|
1899
|
+
const postRes = await axios.post(`${wpUrl}/wp-json/erc/v1/pages/${page_id}/elements/${element_id}`, merged, { headers: { Authorization: authHeader, "Content-Type": "application/json" } });
|
|
1900
|
+
return { content: [{ type: "text", text: JSON.stringify({ order_applied: orderSettings, result: postRes.data }, null, 2) }] };
|
|
1901
|
+
}
|
|
1902
|
+
catch (error) {
|
|
1903
|
+
return { content: [{ type: "text", text: `Error setting element order: ${error.response?.data?.message || error.message}` }], isError: true };
|
|
1904
|
+
}
|
|
1905
|
+
});
|
|
1906
|
+
// ── Group 24: Element Z-Index ─────────────────────────────────────────────────
|
|
1907
|
+
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).", {
|
|
1908
|
+
page_id: z.string().describe("WordPress Page ID"),
|
|
1909
|
+
element_id: z.string().describe("Elementor element ID to update"),
|
|
1910
|
+
z_index: z.number().optional().describe("Absolute Z-index value (e.g. 1, 10, 100). Use this OR reference_element_id — not both."),
|
|
1911
|
+
reference_element_id: z.string().optional().describe("Element ID to compare against. Use with 'position' to set z-index relative to another element."),
|
|
1912
|
+
position: z.enum(["above", "below"]).optional().describe("'above' = z-index of reference + 1, 'below' = z-index of reference - 1"),
|
|
1913
|
+
site: siteParam,
|
|
1914
|
+
}, async ({ page_id, element_id, z_index, reference_element_id, position, site }) => {
|
|
1915
|
+
try {
|
|
1916
|
+
const { wpUrl, authHeader } = resolveSite(sites, site);
|
|
1917
|
+
let finalZIndex = z_index;
|
|
1918
|
+
// If relative mode: fetch reference element's current z-index
|
|
1919
|
+
if (reference_element_id && position) {
|
|
1920
|
+
const refRes = await axios.get(`${wpUrl}/wp-json/erc/v1/pages/${page_id}/elements/${reference_element_id}`, { headers: { Authorization: authHeader } });
|
|
1921
|
+
const refZ = parseInt(refRes.data.settings?._z_index || "0", 10);
|
|
1922
|
+
finalZIndex = position === "above" ? refZ + 1 : Math.max(0, refZ - 1);
|
|
1923
|
+
}
|
|
1924
|
+
if (finalZIndex === undefined) {
|
|
1925
|
+
return { content: [{ type: "text", text: "Provide either z_index or reference_element_id + position." }], isError: true };
|
|
1926
|
+
}
|
|
1927
|
+
const getRes = await axios.get(`${wpUrl}/wp-json/erc/v1/pages/${page_id}/elements/${element_id}`, { headers: { Authorization: authHeader } });
|
|
1928
|
+
const el = getRes.data;
|
|
1929
|
+
const merged = { ...el, settings: { ...(el.settings || {}), _z_index: finalZIndex } };
|
|
1930
|
+
const postRes = await axios.post(`${wpUrl}/wp-json/erc/v1/pages/${page_id}/elements/${element_id}`, merged, { headers: { Authorization: authHeader, "Content-Type": "application/json" } });
|
|
1931
|
+
return { content: [{ type: "text", text: JSON.stringify({ z_index_set: finalZIndex, result: postRes.data }, null, 2) }] };
|
|
1932
|
+
}
|
|
1933
|
+
catch (error) {
|
|
1934
|
+
return { content: [{ type: "text", text: `Error setting Z-index: ${error.response?.data?.message || error.message}` }], isError: true };
|
|
1935
|
+
}
|
|
1936
|
+
});
|
|
1937
|
+
// ── Group 26: Favicon ─────────────────────────────────────────────────────────
|
|
1938
|
+
server.tool("upload-favicon", "Set the WordPress Site Icon (favicon) — equivalent to Customize → Site Identity → Select Site Icon. Accepts either a URL of an image already in the media library, a media attachment ID, or a remote URL to fetch and upload first.", {
|
|
1939
|
+
favicon_url: z.string().optional().describe("URL of the favicon image. If the URL is from the same WP media library, the existing attachment is reused; otherwise the image is downloaded and uploaded as a new media item."),
|
|
1940
|
+
favicon_id: z.number().optional().describe("WordPress media attachment ID of an already-uploaded favicon image (preferred when known)"),
|
|
1941
|
+
site: siteParam,
|
|
1942
|
+
}, async ({ favicon_url, favicon_id, site }) => {
|
|
1943
|
+
try {
|
|
1944
|
+
if (!favicon_url && !favicon_id) {
|
|
1945
|
+
return { content: [{ type: "text", text: "Provide either favicon_url or favicon_id." }], isError: true };
|
|
1946
|
+
}
|
|
1947
|
+
const { wpUrl, authHeader } = resolveSite(sites, site);
|
|
1948
|
+
const body = {};
|
|
1949
|
+
if (favicon_id)
|
|
1950
|
+
body.favicon_id = favicon_id;
|
|
1951
|
+
if (favicon_url)
|
|
1952
|
+
body.favicon_url = favicon_url;
|
|
1953
|
+
const r = await axios.post(`${wpUrl}/wp-json/erc/v1/site/favicon`, body, { headers: { Authorization: authHeader, "Content-Type": "application/json" } });
|
|
1954
|
+
return { content: [{ type: "text", text: JSON.stringify(r.data, null, 2) }] };
|
|
1955
|
+
}
|
|
1956
|
+
catch (error) {
|
|
1957
|
+
return { content: [{ type: "text", text: `Error setting favicon: ${error.response?.data?.message || error.message}` }], isError: true };
|
|
1958
|
+
}
|
|
1959
|
+
});
|
|
1960
|
+
// ── Group 25: Video Overlay ───────────────────────────────────────────────────
|
|
1961
|
+
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.", {
|
|
1962
|
+
page_id: z.string().describe("WordPress Page ID containing the video widget"),
|
|
1963
|
+
widget_id: z.string().describe("Elementor element ID of the video widget"),
|
|
1964
|
+
overlay_image_url: z.string().describe("Full URL of the overlay/thumbnail image (use the video thumbnail or hero section image)"),
|
|
1965
|
+
overlay_image_id: z.number().optional().describe("WordPress media attachment ID of the overlay image (preferred over URL if available)"),
|
|
1966
|
+
show_play_icon: z.boolean().optional().describe("Show a play button icon over the overlay image (default: true)"),
|
|
1967
|
+
site: siteParam,
|
|
1968
|
+
}, async ({ page_id, widget_id, overlay_image_url, overlay_image_id, show_play_icon, site }) => {
|
|
1969
|
+
try {
|
|
1970
|
+
const { wpUrl, authHeader } = resolveSite(sites, site);
|
|
1971
|
+
const getRes = await axios.get(`${wpUrl}/wp-json/erc/v1/pages/${page_id}/elements/${widget_id}`, { headers: { Authorization: authHeader } });
|
|
1972
|
+
const el = getRes.data;
|
|
1973
|
+
const overlaySettings = {
|
|
1974
|
+
show_image_overlay: "yes",
|
|
1975
|
+
image_overlay: {
|
|
1976
|
+
url: overlay_image_url,
|
|
1977
|
+
id: overlay_image_id ?? 0,
|
|
1978
|
+
},
|
|
1979
|
+
show_play_icon: (show_play_icon ?? true) ? "yes" : "no",
|
|
1980
|
+
lightbox: "no", // Ensure video plays inline, not in a lightbox
|
|
1981
|
+
};
|
|
1982
|
+
const merged = { ...el, settings: { ...(el.settings || {}), ...overlaySettings } };
|
|
1983
|
+
const postRes = await axios.post(`${wpUrl}/wp-json/erc/v1/pages/${page_id}/elements/${widget_id}`, merged, { headers: { Authorization: authHeader, "Content-Type": "application/json" } });
|
|
1984
|
+
// Clear page cache so the updated overlay loads immediately
|
|
1985
|
+
await axios.post(`${wpUrl}/wp-json/erc/v1/site/clear-cache`, {}, { headers: { Authorization: authHeader }, params: { post_id: page_id } });
|
|
1986
|
+
return { content: [{ type: "text", text: JSON.stringify({ overlay_image_url, result: postRes.data }, null, 2) }] };
|
|
1987
|
+
}
|
|
1988
|
+
catch (error) {
|
|
1989
|
+
return { content: [{ type: "text", text: `Error updating video overlay: ${error.response?.data?.message || error.message}` }], isError: true };
|
|
1990
|
+
}
|
|
1991
|
+
});
|
|
1785
1992
|
return server;
|
|
1786
1993
|
}
|
|
1787
1994
|
// ─── Entry Point ──────────────────────────────────────────────────────────────
|
package/package.json
CHANGED