@yashwant.dharmdas/elementor-mcp 3.6.0 → 3.8.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 +77 -32
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -195,41 +195,74 @@ function createMcpServer(sites) {
|
|
|
195
195
|
return { content: [{ type: "text", text: `Error fetching page data: ${error.response?.data?.message || error.message}` }], isError: true };
|
|
196
196
|
}
|
|
197
197
|
});
|
|
198
|
-
server.tool("find-elements", "Search the Elementor element tree of a page and return matching elements.
|
|
198
|
+
server.tool("find-elements", "Search the Elementor element tree of a page SERVER-SIDE and return ONLY matching elements — never downloads the full JSON. Use this instead of get-data whenever you need to locate specific widgets. Filters are combined with AND. Example uses: find all buttons (widget_type='button'), find buttons with no link (widget_type='button' + has_empty='link.url'), find headings containing a phrase (widget_type='heading' + contains='Welcome').", {
|
|
199
199
|
page_id: z.string().describe("WordPress Page ID"),
|
|
200
|
-
el_type: z.string().optional().describe("Filter by elType: '
|
|
201
|
-
widget_type: z.string().optional().describe("Filter by widgetType
|
|
202
|
-
contains: z.string().optional().describe("
|
|
203
|
-
|
|
200
|
+
el_type: z.string().optional().describe("Filter by elType: 'widget' or 'container'"),
|
|
201
|
+
widget_type: z.string().optional().describe("Filter by widgetType e.g. 'button', 'heading', 'image', 'text-editor'"),
|
|
202
|
+
contains: z.string().optional().describe("Substring that must appear anywhere in the element's settings JSON"),
|
|
203
|
+
has_empty: z.string().optional().describe("Dot-notation setting path that must be empty/missing — e.g. 'link.url' finds buttons with no URL"),
|
|
204
|
+
return_keys: z.array(z.string()).optional().describe("Limit which settings keys are returned — e.g. ['text','link'] to keep response small"),
|
|
205
|
+
include_path: z.boolean().optional().describe("Include ancestor element IDs in each result"),
|
|
204
206
|
site: siteParam,
|
|
205
|
-
}, async ({ page_id, el_type, widget_type, contains, include_path, site }) => {
|
|
207
|
+
}, async ({ page_id, el_type, widget_type, contains, has_empty, return_keys, include_path, site }) => {
|
|
206
208
|
try {
|
|
207
209
|
const { wpUrl, authHeader } = resolveSite(sites, site);
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
traverse(el.elements, currentPath);
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
traverse(r.data, []);
|
|
227
|
-
return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] };
|
|
210
|
+
const body = {};
|
|
211
|
+
if (el_type)
|
|
212
|
+
body.el_type = el_type;
|
|
213
|
+
if (widget_type)
|
|
214
|
+
body.widget_type = widget_type;
|
|
215
|
+
if (contains)
|
|
216
|
+
body.contains = contains;
|
|
217
|
+
if (has_empty)
|
|
218
|
+
body.has_empty = has_empty;
|
|
219
|
+
if (return_keys)
|
|
220
|
+
body.return_keys = return_keys;
|
|
221
|
+
if (include_path)
|
|
222
|
+
body.include_path = include_path;
|
|
223
|
+
const r = await axios.post(`${wpUrl}/wp-json/erc/v1/pages/${page_id}/find`, body, { headers: { Authorization: authHeader, "Content-Type": "application/json" } });
|
|
224
|
+
return { content: [{ type: "text", text: JSON.stringify(r.data, null, 2) }] };
|
|
228
225
|
}
|
|
229
226
|
catch (error) {
|
|
230
227
|
return { content: [{ type: "text", text: `Error: ${error.response?.data?.message || error.message}` }], isError: true };
|
|
231
228
|
}
|
|
232
229
|
});
|
|
230
|
+
server.tool("set-element-link", "Set, replace, or clear the link on any Elementor widget that has a link (button, heading, image, icon-box, etc.). Updates the NATIVE Elementor link object (url + is_external + nofollow + custom_attributes) — exactly the same fields the editor writes when you set a link in the UI. Use this instead of patch-data or merge-element-settings for any link change; it guarantees 'Open in new tab' uses the native Elementor toggle (is_external) and not a fake HTML attribute.", {
|
|
231
|
+
page_id: z.string().describe("WordPress Page ID"),
|
|
232
|
+
element_id: z.string().describe("Elementor Element ID of the widget whose link you want to change"),
|
|
233
|
+
url: z.string().optional().describe("URL to set, e.g. 'https://example.com'. Required unless clear=true."),
|
|
234
|
+
open_in_new_tab: z.boolean().optional().describe("Native Elementor 'Open in new window' toggle. Maps to link.is_external = 'on'."),
|
|
235
|
+
nofollow: z.boolean().optional().describe("Native Elementor 'Add nofollow' toggle. Maps to link.nofollow = 'on'."),
|
|
236
|
+
custom_attributes: z.string().optional().describe("Custom HTML link attributes, Elementor format: 'key|value,key|value'."),
|
|
237
|
+
clear: z.boolean().optional().describe("Set true to remove the link entirely (url='' and all flags reset)."),
|
|
238
|
+
link_field: z.string().optional().describe("Settings key holding the link object. Default 'link'. Override only for non-standard widgets."),
|
|
239
|
+
site: siteParam,
|
|
240
|
+
}, async ({ page_id, element_id, url, open_in_new_tab, nofollow, custom_attributes, clear, link_field, site }) => {
|
|
241
|
+
try {
|
|
242
|
+
if (!clear && (url === undefined || url === null || String(url).trim() === "")) {
|
|
243
|
+
return { content: [{ type: "text", text: "Provide a non-empty url, OR set clear=true to remove the link." }], isError: true };
|
|
244
|
+
}
|
|
245
|
+
const { wpUrl, authHeader } = resolveSite(sites, site);
|
|
246
|
+
const body = {};
|
|
247
|
+
if (url !== undefined)
|
|
248
|
+
body.url = url;
|
|
249
|
+
if (open_in_new_tab !== undefined)
|
|
250
|
+
body.open_in_new_tab = open_in_new_tab;
|
|
251
|
+
if (nofollow !== undefined)
|
|
252
|
+
body.nofollow = nofollow;
|
|
253
|
+
if (custom_attributes !== undefined)
|
|
254
|
+
body.custom_attributes = custom_attributes;
|
|
255
|
+
if (clear !== undefined)
|
|
256
|
+
body.clear = clear;
|
|
257
|
+
if (link_field !== undefined)
|
|
258
|
+
body.link_field = link_field;
|
|
259
|
+
const r = await axios.post(`${wpUrl}/wp-json/erc/v1/pages/${page_id}/elements/${element_id}/link`, body, { headers: { Authorization: authHeader, "Content-Type": "application/json" } });
|
|
260
|
+
return { content: [{ type: "text", text: JSON.stringify(r.data, null, 2) }] };
|
|
261
|
+
}
|
|
262
|
+
catch (error) {
|
|
263
|
+
return { content: [{ type: "text", text: `Error setting element link: ${error.response?.data?.message || error.message}` }], isError: true };
|
|
264
|
+
}
|
|
265
|
+
});
|
|
233
266
|
server.tool("merge-element-settings", "Deep-merge settings into a specific Elementor element without replacing the full element. Use for small setting adjustments.", {
|
|
234
267
|
page_id: z.string().describe("WordPress Page ID"),
|
|
235
268
|
element_id: z.string().describe("Elementor Element ID"),
|
|
@@ -1935,15 +1968,19 @@ function createMcpServer(sites) {
|
|
|
1935
1968
|
}
|
|
1936
1969
|
});
|
|
1937
1970
|
// ── Group 27: Content Audit (text extraction & bulk fix application) ────────
|
|
1938
|
-
server.tool("extract-pages-text", "Extract clean text content from one or many Elementor pages — returns ONLY the words, not the JSON or HTML noise. Designed for grammar/spelling/sentence audits
|
|
1939
|
-
page_ids: z.array(z.number()).optional().describe("Specific page IDs to extract
|
|
1940
|
-
scope: z.enum(["all_pages"]).optional().describe("Use 'all_pages'
|
|
1941
|
-
post_type: z.string().optional().describe("Limit to a specific post type (e.g. 'page', 'post'
|
|
1971
|
+
server.tool("extract-pages-text", "Extract clean text content from one or many Elementor pages — returns ONLY the words, not the JSON or HTML noise. Designed for grammar/spelling/sentence audits. Uses a key-name heuristic so it works for ANY widget (built-in, Elementor Pro, WooCommerce, third-party, custom). Each block includes id + path so fixes can be written back with apply-text-fixes. ALWAYS use compact=true, min_words=3, max_chars=600 for grammar/spelling audits. ALWAYS use page_ids with 5 pages max per call — never scope='all_pages' (response too large).", {
|
|
1972
|
+
page_ids: z.array(z.number()).optional().describe("Specific page IDs to extract — max 5 per call for grammar audits"),
|
|
1973
|
+
scope: z.enum(["all_pages"]).optional().describe("Use 'all_pages' only for non-audit use cases — returns too much data for grammar checks"),
|
|
1974
|
+
post_type: z.string().optional().describe("Limit to a specific post type (e.g. 'page', 'post')"),
|
|
1975
|
+
compact: z.boolean().optional().describe("true = return only id/path/text per block, omit widget type and is_html. Always true for grammar audits."),
|
|
1976
|
+
min_words: z.number().optional().describe("Skip text blocks shorter than N words. Default 1. Use 3 for grammar audits to skip buttons and labels."),
|
|
1977
|
+
max_chars: z.number().optional().describe("Truncate each text block at N characters. Default 0 (no limit). Use 600 for grammar audits."),
|
|
1978
|
+
max_pages: z.number().optional().describe("Hard cap on pages processed per call. Safety net — use page_ids batching instead."),
|
|
1942
1979
|
site: siteParam,
|
|
1943
|
-
}, async ({ page_ids, scope, post_type, site }) => {
|
|
1980
|
+
}, async ({ page_ids, scope, post_type, compact, min_words, max_chars, max_pages, site }) => {
|
|
1944
1981
|
try {
|
|
1945
1982
|
if (!page_ids && !scope && !post_type) {
|
|
1946
|
-
return { content: [{ type: "text", text: "Provide page_ids, scope='all_pages', or post_type." }], isError: true };
|
|
1983
|
+
return { content: [{ type: "text", text: "Provide page_ids (max 5 per call), scope='all_pages', or post_type." }], isError: true };
|
|
1947
1984
|
}
|
|
1948
1985
|
const { wpUrl, authHeader } = resolveSite(sites, site);
|
|
1949
1986
|
const body = {};
|
|
@@ -1953,6 +1990,14 @@ function createMcpServer(sites) {
|
|
|
1953
1990
|
body.scope = scope;
|
|
1954
1991
|
if (post_type)
|
|
1955
1992
|
body.post_type = post_type;
|
|
1993
|
+
if (compact !== undefined)
|
|
1994
|
+
body.compact = compact;
|
|
1995
|
+
if (min_words !== undefined)
|
|
1996
|
+
body.min_words = min_words;
|
|
1997
|
+
if (max_chars !== undefined)
|
|
1998
|
+
body.max_chars = max_chars;
|
|
1999
|
+
if (max_pages !== undefined)
|
|
2000
|
+
body.max_pages = max_pages;
|
|
1956
2001
|
const r = await axios.post(`${wpUrl}/wp-json/erc/v1/site/extract-text`, body, { headers: { Authorization: authHeader, "Content-Type": "application/json" } });
|
|
1957
2002
|
return { content: [{ type: "text", text: JSON.stringify(r.data, null, 2) }] };
|
|
1958
2003
|
}
|
package/package.json
CHANGED