@elementor/elementor-v3-mcp 4.1.0-754
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.d.mts +70 -0
- package/dist/index.d.ts +70 -0
- package/dist/index.js +1161 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1125 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +50 -0
- package/src/context.ts +211 -0
- package/src/elementor-mcp-server.ts +95 -0
- package/src/index.ts +22 -0
- package/src/init.ts +5 -0
- package/src/resources.ts +186 -0
- package/src/tools/ai-tool.ts +58 -0
- package/src/tools/dynamic-tool.ts +236 -0
- package/src/tools/index.ts +6 -0
- package/src/tools/page-tool.ts +222 -0
- package/src/tools/routes-tool.ts +138 -0
- package/src/tools/styling-tool.ts +131 -0
- package/src/tools/ui-tool.ts +100 -0
- package/src/types.ts +120 -0
- package/src/utils.ts +211 -0
- package/src/validation-helpers.ts +29 -0
- package/src/widget-mandatory-fields.ts +68 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1125 @@
|
|
|
1
|
+
// src/elementor-mcp-server.ts
|
|
2
|
+
import { getAngieSdk } from "@elementor/editor-mcp";
|
|
3
|
+
import { waitForElementorEditor } from "@elementor/elementor-mcp-common";
|
|
4
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
|
+
|
|
6
|
+
// src/resources.ts
|
|
7
|
+
import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
8
|
+
|
|
9
|
+
// src/utils.ts
|
|
10
|
+
var getElementor = () => window.elementor;
|
|
11
|
+
var get$e = () => window.$e;
|
|
12
|
+
var getElementorCommon = () => window.elementorCommon;
|
|
13
|
+
function encodeToolJson(data) {
|
|
14
|
+
return JSON.stringify(data).replaceAll('"', "'");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// src/context.ts
|
|
18
|
+
function deepMerge(target, source) {
|
|
19
|
+
if (!source || typeof source !== "object") {
|
|
20
|
+
return target;
|
|
21
|
+
}
|
|
22
|
+
if (!target || typeof target !== "object") {
|
|
23
|
+
return source;
|
|
24
|
+
}
|
|
25
|
+
const result = { ...target };
|
|
26
|
+
for (const key of Object.keys(source)) {
|
|
27
|
+
const sourceValue = source[key];
|
|
28
|
+
const targetValue = target[key];
|
|
29
|
+
if (Array.isArray(sourceValue)) {
|
|
30
|
+
result[key] = sourceValue;
|
|
31
|
+
} else if (sourceValue && typeof sourceValue === "object") {
|
|
32
|
+
result[key] = deepMerge(targetValue, sourceValue);
|
|
33
|
+
} else {
|
|
34
|
+
result[key] = sourceValue;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
39
|
+
function getPageOverView() {
|
|
40
|
+
const document = getElementor()?.documents?.getCurrent();
|
|
41
|
+
if (!document) {
|
|
42
|
+
return { error: "No active document found" };
|
|
43
|
+
}
|
|
44
|
+
function extractElementData(element) {
|
|
45
|
+
if (!element || !element.model) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
const model = element.model.attributes;
|
|
49
|
+
if (!model) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
const result = {
|
|
53
|
+
id: model.id,
|
|
54
|
+
elType: model.elType,
|
|
55
|
+
widgetType: model.widgetType || void 0
|
|
56
|
+
};
|
|
57
|
+
const title = model.title || element.model.editor_settings?.title;
|
|
58
|
+
if (title) {
|
|
59
|
+
result.title = title;
|
|
60
|
+
}
|
|
61
|
+
if (element.children && element.children.length > 0) {
|
|
62
|
+
result.children = element.children.map((child) => extractElementData(child)).filter((child) => child !== null);
|
|
63
|
+
}
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
const containers = document.container.children ?? [];
|
|
67
|
+
const elements = containers.map((container) => extractElementData(container));
|
|
68
|
+
return {
|
|
69
|
+
documentId: document.id,
|
|
70
|
+
documentType: document.config?.type ?? "unknown",
|
|
71
|
+
title: document.config?.settings?.post_title || "Untitled",
|
|
72
|
+
elements: elements.filter((el) => el !== null)
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function mapControlsToSchema(controlsData) {
|
|
76
|
+
if (!controlsData || typeof controlsData !== "object") {
|
|
77
|
+
return {};
|
|
78
|
+
}
|
|
79
|
+
return Object.fromEntries(
|
|
80
|
+
Object.entries(controlsData).filter(([, control]) => !["section", "tab", "tabs"].includes(control.type)).map(([controlKey, control]) => {
|
|
81
|
+
let options;
|
|
82
|
+
const controlConfig = getElementor()?.config?.controls?.[control.type] || {};
|
|
83
|
+
const completeConfig = deepMerge(controlConfig, control);
|
|
84
|
+
const returnValue = completeConfig.return_value;
|
|
85
|
+
if (completeConfig.options) {
|
|
86
|
+
options = Object.keys(completeConfig.options);
|
|
87
|
+
}
|
|
88
|
+
let fields;
|
|
89
|
+
if (completeConfig.fields) {
|
|
90
|
+
fields = mapControlsToSchema(completeConfig.fields);
|
|
91
|
+
}
|
|
92
|
+
const returnConfig = {
|
|
93
|
+
type: control.type,
|
|
94
|
+
default: completeConfig.default
|
|
95
|
+
};
|
|
96
|
+
if (options) {
|
|
97
|
+
returnConfig.options = options;
|
|
98
|
+
}
|
|
99
|
+
if (fields) {
|
|
100
|
+
returnConfig.fields = fields;
|
|
101
|
+
}
|
|
102
|
+
if (completeConfig.size_units) {
|
|
103
|
+
returnConfig.size_units = completeConfig.size_units;
|
|
104
|
+
}
|
|
105
|
+
if (returnValue !== void 0) {
|
|
106
|
+
returnConfig.onValue = returnValue;
|
|
107
|
+
}
|
|
108
|
+
return [controlKey, returnConfig];
|
|
109
|
+
})
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
function getDocumentSchema(documentId) {
|
|
113
|
+
const document = getElementor()?.documents?.get?.(documentId);
|
|
114
|
+
if (!document) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const controls = document.config?.settings?.controls;
|
|
118
|
+
return mapControlsToSchema(controls);
|
|
119
|
+
}
|
|
120
|
+
function loadDocumentSettings(documentId) {
|
|
121
|
+
const document = getElementor()?.documents?.get?.(documentId);
|
|
122
|
+
if (!document) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const allSettings = document.config?.settings?.settings || {};
|
|
126
|
+
const controls = document.config?.settings?.controls || {};
|
|
127
|
+
const result = {};
|
|
128
|
+
Object.entries(allSettings).forEach(([controlKey, value]) => {
|
|
129
|
+
const control = controls[controlKey];
|
|
130
|
+
if (value !== control?.default) {
|
|
131
|
+
result[controlKey] = value;
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
return result;
|
|
135
|
+
}
|
|
136
|
+
function loadDocumentSchema(documentId) {
|
|
137
|
+
return getDocumentSchema(documentId);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// src/resources.ts
|
|
141
|
+
var RESOURCE_NAME_ELEMENT_SETTINGS = "elementor-element-settings";
|
|
142
|
+
var RESOURCE_URI_ELEMENT_SETTINGS_TEMPLATE = "elementor://editor/element-settings/{elementId}";
|
|
143
|
+
var RESOURCE_NAME_WIDGET_CONFIG = "elementor-widget-config";
|
|
144
|
+
var RESOURCE_URI_WIDGET_CONFIG_TEMPLATE = "elementor://editor/widget-config/{widgetType}";
|
|
145
|
+
var RESOURCE_NAME_PAGE_OVERVIEW = "elementor-page-overview";
|
|
146
|
+
var RESOURCE_URI_PAGE_OVERVIEW = "elementor://editor/page-overview";
|
|
147
|
+
var RESOURCE_NAME_PAGE_SETTINGS = "elementor-page-settings";
|
|
148
|
+
var RESOURCE_URI_PAGE_SETTINGS = "elementor://editor/page-settings";
|
|
149
|
+
function decodeResourceVariable(value) {
|
|
150
|
+
try {
|
|
151
|
+
let decoded = decodeURIComponent(value).replace(/[·]/g, "");
|
|
152
|
+
decoded = decoded.replace(/^{|}$/g, "");
|
|
153
|
+
return decoded;
|
|
154
|
+
} catch {
|
|
155
|
+
return value;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
async function handleGetWidgetSettings(params) {
|
|
159
|
+
const elementor = getElementor();
|
|
160
|
+
const container = elementor?.getContainer(params.elementId);
|
|
161
|
+
if (!container) {
|
|
162
|
+
throw new Error(`Element with ID ${params.elementId} not found.`);
|
|
163
|
+
}
|
|
164
|
+
const settings = container.settings.attributes || {};
|
|
165
|
+
return {
|
|
166
|
+
content: [{ type: "text", text: encodeToolJson(settings) }]
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
async function handleGetWidgetSchema(params) {
|
|
170
|
+
const elementor = getElementor();
|
|
171
|
+
const controls = elementor?.widgetsCache[params.widgetType]?.controls;
|
|
172
|
+
if (!controls) {
|
|
173
|
+
throw new Error(`Widget type ${params.widgetType} not found.`);
|
|
174
|
+
}
|
|
175
|
+
return {
|
|
176
|
+
content: [{ type: "text", text: encodeToolJson(controls) }]
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
function addElementorResources(server) {
|
|
180
|
+
server.resource(
|
|
181
|
+
RESOURCE_NAME_PAGE_OVERVIEW,
|
|
182
|
+
RESOURCE_URI_PAGE_OVERVIEW,
|
|
183
|
+
{
|
|
184
|
+
description: "Complete page structure showing all elements, containers, and their hierarchical relationships on the current Elementor page"
|
|
185
|
+
},
|
|
186
|
+
async (uri) => {
|
|
187
|
+
const overview = getPageOverView();
|
|
188
|
+
if (!overview || overview.error) {
|
|
189
|
+
throw new Error(overview?.error || "Failed to retrieve page overview");
|
|
190
|
+
}
|
|
191
|
+
return {
|
|
192
|
+
contents: [
|
|
193
|
+
{
|
|
194
|
+
uri: uri.toString(),
|
|
195
|
+
mimeType: "application/json",
|
|
196
|
+
text: encodeToolJson(overview)
|
|
197
|
+
}
|
|
198
|
+
]
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
);
|
|
202
|
+
server.resource(
|
|
203
|
+
RESOURCE_NAME_PAGE_SETTINGS,
|
|
204
|
+
RESOURCE_URI_PAGE_SETTINGS,
|
|
205
|
+
{
|
|
206
|
+
description: "Page/document settings schema and current values including title, template, margins, padding, and backgrounds"
|
|
207
|
+
},
|
|
208
|
+
async (uri) => {
|
|
209
|
+
const elementor = getElementor();
|
|
210
|
+
const currentDocument = elementor?.documents?.getCurrent();
|
|
211
|
+
if (!currentDocument) {
|
|
212
|
+
throw new Error("No active document found");
|
|
213
|
+
}
|
|
214
|
+
const documentSchema = loadDocumentSchema(currentDocument.id);
|
|
215
|
+
const documentSettings = loadDocumentSettings(currentDocument.id);
|
|
216
|
+
if (!documentSchema || !documentSettings) {
|
|
217
|
+
throw new Error("Failed to retrieve page settings");
|
|
218
|
+
}
|
|
219
|
+
const result = {
|
|
220
|
+
schema: documentSchema,
|
|
221
|
+
currentSettings: documentSettings
|
|
222
|
+
};
|
|
223
|
+
return {
|
|
224
|
+
contents: [
|
|
225
|
+
{
|
|
226
|
+
uri: uri.toString(),
|
|
227
|
+
mimeType: "application/json",
|
|
228
|
+
text: encodeToolJson(result)
|
|
229
|
+
}
|
|
230
|
+
]
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
);
|
|
234
|
+
server.resource(
|
|
235
|
+
RESOURCE_NAME_ELEMENT_SETTINGS,
|
|
236
|
+
new ResourceTemplate(RESOURCE_URI_ELEMENT_SETTINGS_TEMPLATE, {
|
|
237
|
+
list: void 0
|
|
238
|
+
}),
|
|
239
|
+
{
|
|
240
|
+
description: "Complete settings schema and current values for a specific element, including all available configuration options and their current state"
|
|
241
|
+
},
|
|
242
|
+
async (uri, variables) => {
|
|
243
|
+
let elementId = Array.isArray(variables.elementId) ? variables.elementId[0] : variables.elementId;
|
|
244
|
+
if (!elementId) {
|
|
245
|
+
throw new Error("Element ID is required");
|
|
246
|
+
}
|
|
247
|
+
elementId = decodeResourceVariable(elementId);
|
|
248
|
+
const result = await handleGetWidgetSettings({ elementId, action: "get-widget-settings" });
|
|
249
|
+
return {
|
|
250
|
+
contents: [
|
|
251
|
+
{
|
|
252
|
+
uri: uri.toString(),
|
|
253
|
+
mimeType: "text/plain",
|
|
254
|
+
text: result.content[0].text
|
|
255
|
+
}
|
|
256
|
+
]
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
);
|
|
260
|
+
server.resource(
|
|
261
|
+
RESOURCE_NAME_WIDGET_CONFIG,
|
|
262
|
+
new ResourceTemplate(RESOURCE_URI_WIDGET_CONFIG_TEMPLATE, {
|
|
263
|
+
list: void 0
|
|
264
|
+
}),
|
|
265
|
+
{
|
|
266
|
+
description: "Complete configuration schema for a specific widget type, showing all available settings, properties, and their expected formats"
|
|
267
|
+
},
|
|
268
|
+
async (uri, variables) => {
|
|
269
|
+
let widgetType = Array.isArray(variables.widgetType) ? variables.widgetType[0] : variables.widgetType;
|
|
270
|
+
if (!widgetType) {
|
|
271
|
+
throw new Error("Widget type is required");
|
|
272
|
+
}
|
|
273
|
+
widgetType = decodeResourceVariable(widgetType);
|
|
274
|
+
const result = await handleGetWidgetSchema({ widgetType, action: "get-widget-schema" });
|
|
275
|
+
return {
|
|
276
|
+
contents: [
|
|
277
|
+
{
|
|
278
|
+
uri: uri.toString(),
|
|
279
|
+
mimeType: "text/plain",
|
|
280
|
+
text: result.content[0].text
|
|
281
|
+
}
|
|
282
|
+
]
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// src/tools/ai-tool.ts
|
|
289
|
+
import { z } from "@elementor/schema";
|
|
290
|
+
function addAiTool(server) {
|
|
291
|
+
server.registerTool(
|
|
292
|
+
"ai",
|
|
293
|
+
{
|
|
294
|
+
description: "Manage Elementor AI integration features and interfaces.",
|
|
295
|
+
inputSchema: {
|
|
296
|
+
action: z.enum(["open-brand-voice", "open-choose-element", "open-text-to-elementor"]).describe("The AI operation to perform")
|
|
297
|
+
},
|
|
298
|
+
annotations: {
|
|
299
|
+
title: "Manage AI Integration"
|
|
300
|
+
}
|
|
301
|
+
},
|
|
302
|
+
async (params) => {
|
|
303
|
+
switch (params.action) {
|
|
304
|
+
case "open-brand-voice":
|
|
305
|
+
return await handleOpenBrandVoice();
|
|
306
|
+
case "open-choose-element":
|
|
307
|
+
return await handleOpenChooseElement();
|
|
308
|
+
case "open-text-to-elementor":
|
|
309
|
+
return await handleOpenTextToElementor();
|
|
310
|
+
default:
|
|
311
|
+
throw new Error(`Unknown action: ${params.action}`);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
async function handleOpenBrandVoice() {
|
|
317
|
+
await get$e()?.run("ai-integration/open-brand-voice");
|
|
318
|
+
return {
|
|
319
|
+
content: [{ type: "text", text: "Brand Voice interface opened." }]
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
async function handleOpenChooseElement() {
|
|
323
|
+
await get$e()?.run("ai-integration/open-choose-element");
|
|
324
|
+
return {
|
|
325
|
+
content: [{ type: "text", text: "Choose Element interface opened." }]
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
async function handleOpenTextToElementor() {
|
|
329
|
+
await get$e()?.run("ai-integration/open-text-to-elementor");
|
|
330
|
+
return {
|
|
331
|
+
content: [{ type: "text", text: "Text to Elementor interface opened." }]
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// src/tools/dynamic-tool.ts
|
|
336
|
+
import { z as z2 } from "@elementor/schema";
|
|
337
|
+
|
|
338
|
+
// src/validation-helpers.ts
|
|
339
|
+
function validateDocumentSettingsUpdated(container) {
|
|
340
|
+
const updatedSettings = container.settings.attributes;
|
|
341
|
+
if (!updatedSettings) {
|
|
342
|
+
throw new Error("Document settings update failed: Settings not accessible");
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
function validateDynamicTagEnabled(container, controlName) {
|
|
346
|
+
const modelSettings = container.model?.attributes?.settings;
|
|
347
|
+
const controlValue = modelSettings?.[controlName];
|
|
348
|
+
const hasDynamic = controlValue && typeof controlValue === "object" && "__dynamic__" in controlValue;
|
|
349
|
+
if (!hasDynamic) {
|
|
350
|
+
throw new Error(`Dynamic tag enable failed: Tag not found for ${controlName}`);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
function validateDynamicTagDisabled(container, controlName) {
|
|
354
|
+
const modelSettings = container.model?.attributes?.settings;
|
|
355
|
+
const controlValue = modelSettings?.[controlName];
|
|
356
|
+
const stillHasDynamic = controlValue && typeof controlValue === "object" && "__dynamic__" in controlValue;
|
|
357
|
+
if (stillHasDynamic) {
|
|
358
|
+
throw new Error(`Dynamic tag disable failed: Tag still exists for ${controlName}`);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// src/tools/dynamic-tool.ts
|
|
363
|
+
function addDynamicTool(server) {
|
|
364
|
+
server.registerTool(
|
|
365
|
+
"dynamic",
|
|
366
|
+
{
|
|
367
|
+
description: "Manage dynamic-tags content for Elementor elements including getting dynamic settings, enabling and disabling dynamic tags.",
|
|
368
|
+
inputSchema: {
|
|
369
|
+
action: z2.enum(["get-settings", "enable", "disable"]).describe("The dynamic content operation to perform"),
|
|
370
|
+
elementId: z2.string().describe("The ID of the element to modify"),
|
|
371
|
+
controlName: z2.string().describe("The name of the control/setting to make dynamic"),
|
|
372
|
+
dynamicName: z2.string().optional().describe(
|
|
373
|
+
"The name of the dynamic tag to enable. Required for enable action. Output of get-settings action."
|
|
374
|
+
),
|
|
375
|
+
settings: z2.object({}).catchall(z2.unknown()).optional().describe(
|
|
376
|
+
"The settings to apply to the dynamic tag. Used with enable action. Output of get-settings action."
|
|
377
|
+
),
|
|
378
|
+
hasRunGetDynamicSettings: z2.boolean().optional().describe(
|
|
379
|
+
"Whether the get-settings action has already been run. Must be set to true when using enable action."
|
|
380
|
+
)
|
|
381
|
+
},
|
|
382
|
+
annotations: {
|
|
383
|
+
title: "Manage Dynamic Content"
|
|
384
|
+
}
|
|
385
|
+
},
|
|
386
|
+
async (params) => {
|
|
387
|
+
switch (params.action) {
|
|
388
|
+
case "get-settings":
|
|
389
|
+
return await handleGetDynamicSettings(params);
|
|
390
|
+
case "enable":
|
|
391
|
+
if (params.hasRunGetDynamicSettings !== true) {
|
|
392
|
+
throw new Error(
|
|
393
|
+
"get-dynamic-settings action has not been run. Run it first before using the enable action."
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
if (!params.elementId || !params.controlName || !params.dynamicName || !params.settings) {
|
|
397
|
+
throw new Error(
|
|
398
|
+
"elementId, controlName, dynamicName, and settings are required for dynamic enable"
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
return await handleDynamicEnable(params);
|
|
402
|
+
case "disable":
|
|
403
|
+
if (!params.elementId || !params.controlName) {
|
|
404
|
+
throw new Error("elementId and controlName are required for dynamic disable");
|
|
405
|
+
}
|
|
406
|
+
return await handleDynamicDisable(params);
|
|
407
|
+
default:
|
|
408
|
+
throw new Error(`Unknown action: ${params.action}`);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
async function handleGetDynamicSettings(params) {
|
|
414
|
+
if (!params.elementId || !params.controlName) {
|
|
415
|
+
throw new Error("elementId and controlName are required for get-settings");
|
|
416
|
+
}
|
|
417
|
+
const elementor = getElementor();
|
|
418
|
+
const container = elementor?.getContainer(params.elementId);
|
|
419
|
+
if (!container) {
|
|
420
|
+
throw new Error(`Element with ID ${params.elementId} not found.`);
|
|
421
|
+
}
|
|
422
|
+
const controls = container.settings.controls;
|
|
423
|
+
const control = controls[params.controlName];
|
|
424
|
+
if (!control) {
|
|
425
|
+
throw new Error(`Control "${params.controlName}" not found on element ${params.elementId}.`);
|
|
426
|
+
}
|
|
427
|
+
if (!control.dynamic?.categories) {
|
|
428
|
+
throw new Error(`Control "${params.controlName}" does not support dynamic content.`);
|
|
429
|
+
}
|
|
430
|
+
const { categories } = control.dynamic;
|
|
431
|
+
const dynamicTags = elementor?.dynamicTags;
|
|
432
|
+
if (!dynamicTags?.getConfig) {
|
|
433
|
+
throw new Error("Dynamic tags API is not available.");
|
|
434
|
+
}
|
|
435
|
+
const tags = dynamicTags.getConfig("tags");
|
|
436
|
+
const relevantTags = Object.values(tags).filter(
|
|
437
|
+
(tag) => tag.categories.find((category) => categories.includes(category))
|
|
438
|
+
);
|
|
439
|
+
return {
|
|
440
|
+
content: [{ type: "text", text: JSON.stringify(relevantTags, null, 2) }]
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
async function handleDynamicEnable(params) {
|
|
444
|
+
if (!params.elementId || !params.controlName || !params.dynamicName) {
|
|
445
|
+
throw new Error("elementId, controlName, and dynamicName are required for dynamic enable");
|
|
446
|
+
}
|
|
447
|
+
if (params.hasRunGetDynamicSettings !== true) {
|
|
448
|
+
throw new Error("get-dynamic-settings action has not been run. Run it first before using the enable action.");
|
|
449
|
+
}
|
|
450
|
+
const elementor = getElementor();
|
|
451
|
+
const container = elementor?.getContainer(params.elementId);
|
|
452
|
+
if (!container) {
|
|
453
|
+
throw new Error(`Element with ID ${params.elementId} not found.`);
|
|
454
|
+
}
|
|
455
|
+
const dynamicName = params.dynamicName.toLowerCase().replace(/\s+/g, "-").replace(/_/g, "-").replace(/[^a-z0-9-]/g, "");
|
|
456
|
+
const settings = params.settings || {};
|
|
457
|
+
settings.toJSON = () => settings;
|
|
458
|
+
const elementorCommon = getElementorCommon();
|
|
459
|
+
if (!elementorCommon?.helpers?.getUniqueId) {
|
|
460
|
+
throw new Error("Elementor Common API is not available.");
|
|
461
|
+
}
|
|
462
|
+
const uniqueId = elementorCommon.helpers.getUniqueId();
|
|
463
|
+
const dynamicTags = elementor?.dynamicTags;
|
|
464
|
+
if (!dynamicTags?.tagDataToTagText) {
|
|
465
|
+
throw new Error("Dynamic tags API is not available.");
|
|
466
|
+
}
|
|
467
|
+
const tagText = dynamicTags.tagDataToTagText(String(uniqueId), dynamicName, settings);
|
|
468
|
+
await get$e()?.run("document/dynamic/enable", {
|
|
469
|
+
container,
|
|
470
|
+
settings: { [params.controlName]: tagText }
|
|
471
|
+
});
|
|
472
|
+
validateDynamicTagEnabled(container, params.controlName);
|
|
473
|
+
return {
|
|
474
|
+
content: [
|
|
475
|
+
{
|
|
476
|
+
type: "text",
|
|
477
|
+
text: `Dynamic content enabled for element ${params.elementId}, control "${params.controlName}" with dynamic tag "${dynamicName}": ${tagText}`
|
|
478
|
+
}
|
|
479
|
+
]
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
async function handleDynamicDisable(params) {
|
|
483
|
+
if (!params.elementId || !params.controlName) {
|
|
484
|
+
throw new Error("elementId and controlName are required for dynamic disable");
|
|
485
|
+
}
|
|
486
|
+
const container = getElementor()?.getContainer(params.elementId);
|
|
487
|
+
if (!container) {
|
|
488
|
+
throw new Error(`Element with ID ${params.elementId} not found.`);
|
|
489
|
+
}
|
|
490
|
+
const getElementDynamicSetting = (elementContainer) => {
|
|
491
|
+
const modelSettings = elementContainer.model?.attributes?.settings || {};
|
|
492
|
+
const dynamicContent = {};
|
|
493
|
+
Object.keys(modelSettings).forEach((key) => {
|
|
494
|
+
const value = modelSettings[key];
|
|
495
|
+
let dynamicData = null;
|
|
496
|
+
if (value && typeof value === "object" && value.__dynamic__) {
|
|
497
|
+
dynamicData = value.__dynamic__;
|
|
498
|
+
}
|
|
499
|
+
if (dynamicData) {
|
|
500
|
+
dynamicContent[key] = dynamicData;
|
|
501
|
+
}
|
|
502
|
+
});
|
|
503
|
+
return {
|
|
504
|
+
settingsNames: Object.keys(
|
|
505
|
+
dynamicContent.attributes || {}
|
|
506
|
+
),
|
|
507
|
+
dynamicContent
|
|
508
|
+
};
|
|
509
|
+
};
|
|
510
|
+
const availableDynamicSettingNames = getElementDynamicSetting(container).settingsNames;
|
|
511
|
+
if (!availableDynamicSettingNames.includes(params.controlName)) {
|
|
512
|
+
throw new Error(
|
|
513
|
+
`Setting "${params.controlName}" on element ${params.elementId} does not have dynamic content enabled. here is the list of dynamic settings available: ${JSON.stringify(
|
|
514
|
+
availableDynamicSettingNames,
|
|
515
|
+
null,
|
|
516
|
+
2
|
|
517
|
+
)}`
|
|
518
|
+
);
|
|
519
|
+
}
|
|
520
|
+
await get$e()?.run("document/dynamic/disable", {
|
|
521
|
+
container,
|
|
522
|
+
settings: {
|
|
523
|
+
[params.controlName]: ""
|
|
524
|
+
}
|
|
525
|
+
});
|
|
526
|
+
validateDynamicTagDisabled(container, params.controlName);
|
|
527
|
+
return {
|
|
528
|
+
content: [
|
|
529
|
+
{
|
|
530
|
+
type: "text",
|
|
531
|
+
text: `Dynamic content disabled for setting "${params.controlName}" on element ${params.elementId}.`
|
|
532
|
+
}
|
|
533
|
+
]
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// src/tools/page-tool.ts
|
|
538
|
+
import { z as z3 } from "@elementor/schema";
|
|
539
|
+
function addPageTool(server) {
|
|
540
|
+
server.registerTool(
|
|
541
|
+
"page",
|
|
542
|
+
{
|
|
543
|
+
description: `Manage page and document operations including undo/redo, saving, page settings, and page information. Use this tool when you need to:
|
|
544
|
+
- Undo or redo changes (history-undo, history-redo, history-undo-all) - Use these when user asks to undo, revert, or redo recent changes
|
|
545
|
+
- Save page changes (save-draft, save-publish, save-update, save-discard)
|
|
546
|
+
- Get or update page settings like page title, description, keywords, styling (get-settings, update-settings)
|
|
547
|
+
- Open or preview pages (open, preview)
|
|
548
|
+
This tool handles document-level operations and change history.`,
|
|
549
|
+
inputSchema: {
|
|
550
|
+
action: z3.enum([
|
|
551
|
+
"save-draft",
|
|
552
|
+
"save-publish",
|
|
553
|
+
"save-update",
|
|
554
|
+
"save-discard",
|
|
555
|
+
"history-undo",
|
|
556
|
+
"history-redo",
|
|
557
|
+
"history-undo-all",
|
|
558
|
+
"get-settings",
|
|
559
|
+
"update-settings",
|
|
560
|
+
"open",
|
|
561
|
+
"preview"
|
|
562
|
+
]).describe(
|
|
563
|
+
'The page operation to perform: history-undo (revert the last change - use when user says "undo"), history-redo (reapply last undone change - use when user says "redo"), history-undo-all (revert all changes), save-draft (save as draft), save-publish (publish page), save-update (update published page), save-discard (discard unsaved changes), get-settings (retrieve page settings), update-settings (modify page settings), open (open a different page), preview (preview the page)'
|
|
564
|
+
),
|
|
565
|
+
pageId: z3.string().optional().describe("Page/document ID for open action"),
|
|
566
|
+
settings: z3.record(z3.unknown()).optional().describe(
|
|
567
|
+
'Settings object containing the specific page settings you want to update. REQUIRED for update-settings action. Only include settings you want to change, not all settings. Example: {"hide_title": "yes", "template": "elementor_canvas"}.'
|
|
568
|
+
)
|
|
569
|
+
},
|
|
570
|
+
annotations: {
|
|
571
|
+
title: "Manage Page"
|
|
572
|
+
},
|
|
573
|
+
_meta: {
|
|
574
|
+
"angie:required-resources": [
|
|
575
|
+
{
|
|
576
|
+
uri: RESOURCE_URI_PAGE_SETTINGS,
|
|
577
|
+
whenToUse: "When updating page settings (action=update-settings) to understand the page schema, available settings, their allowed values, and current page configuration"
|
|
578
|
+
}
|
|
579
|
+
]
|
|
580
|
+
}
|
|
581
|
+
},
|
|
582
|
+
async (params) => {
|
|
583
|
+
switch (params.action) {
|
|
584
|
+
case "save-draft":
|
|
585
|
+
return await handleSavePageDraft();
|
|
586
|
+
case "save-publish":
|
|
587
|
+
return await handleSavePagePublish();
|
|
588
|
+
case "save-update":
|
|
589
|
+
return await handleSavePageUpdate();
|
|
590
|
+
case "save-discard":
|
|
591
|
+
return await handleSavePageDiscard();
|
|
592
|
+
case "history-undo":
|
|
593
|
+
return await handleHistoryUndo();
|
|
594
|
+
case "history-redo":
|
|
595
|
+
return await handleHistoryRedo();
|
|
596
|
+
case "history-undo-all":
|
|
597
|
+
return await handleHistoryUndoAll();
|
|
598
|
+
case "get-settings":
|
|
599
|
+
return await handleGetDocumentSettings();
|
|
600
|
+
case "update-settings":
|
|
601
|
+
return await handleUpdateDocumentSettings(params);
|
|
602
|
+
case "open":
|
|
603
|
+
return await handleOpenPage(params);
|
|
604
|
+
case "preview":
|
|
605
|
+
return await handlePreviewPage();
|
|
606
|
+
default:
|
|
607
|
+
throw new Error(`Unknown action: ${params.action}`);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
);
|
|
611
|
+
}
|
|
612
|
+
async function handleSavePageDraft() {
|
|
613
|
+
await get$e()?.run("document/save/draft");
|
|
614
|
+
return {
|
|
615
|
+
content: [{ type: "text", text: "Page saved as draft." }]
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
async function handleSavePagePublish() {
|
|
619
|
+
await get$e()?.run("document/save/publish");
|
|
620
|
+
return {
|
|
621
|
+
content: [{ type: "text", text: "Page published." }]
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
async function handleSavePageUpdate() {
|
|
625
|
+
await get$e()?.run("document/save/update");
|
|
626
|
+
return {
|
|
627
|
+
content: [{ type: "text", text: "Page updated." }]
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
async function handleSavePageDiscard() {
|
|
631
|
+
await get$e()?.run("document/save/discard");
|
|
632
|
+
return {
|
|
633
|
+
content: [{ type: "text", text: "Page changes discarded." }]
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
async function handleHistoryUndo() {
|
|
637
|
+
await get$e()?.run("document/history/undo");
|
|
638
|
+
return {
|
|
639
|
+
content: [{ type: "text", text: "Undo performed." }]
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
async function handleHistoryRedo() {
|
|
643
|
+
await get$e()?.run("document/history/redo");
|
|
644
|
+
return {
|
|
645
|
+
content: [{ type: "text", text: "Redo performed." }]
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
async function handleHistoryUndoAll() {
|
|
649
|
+
await get$e()?.run("document/history/undo-all", { document: getElementor()?.documents.getCurrent() });
|
|
650
|
+
return {
|
|
651
|
+
content: [{ type: "text", text: "All changes undone." }]
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
async function handleOpenPage(params) {
|
|
655
|
+
if (!params.pageId) {
|
|
656
|
+
throw new Error("pageId is required for open action");
|
|
657
|
+
}
|
|
658
|
+
await get$e()?.run("editor/documents/open", {
|
|
659
|
+
id: params.pageId
|
|
660
|
+
});
|
|
661
|
+
return {
|
|
662
|
+
content: [{ type: "text", text: `Page ${params.pageId} opened.` }]
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
async function handlePreviewPage() {
|
|
666
|
+
await get$e()?.run("editor/documents/preview");
|
|
667
|
+
return {
|
|
668
|
+
content: [{ type: "text", text: "Page preview opened." }]
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
async function handleGetDocumentSettings() {
|
|
672
|
+
const document = getElementor()?.documents?.getCurrent();
|
|
673
|
+
if (!document) {
|
|
674
|
+
throw new Error("No active document found.");
|
|
675
|
+
}
|
|
676
|
+
const settings = document.config?.settings || {};
|
|
677
|
+
return {
|
|
678
|
+
content: [
|
|
679
|
+
{
|
|
680
|
+
type: "text",
|
|
681
|
+
text: encodeToolJson({
|
|
682
|
+
documentId: document.id,
|
|
683
|
+
documentType: document.config?.type,
|
|
684
|
+
settings
|
|
685
|
+
})
|
|
686
|
+
}
|
|
687
|
+
]
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
async function handleUpdateDocumentSettings(params) {
|
|
691
|
+
const currentDocument = getElementor()?.documents?.getCurrent();
|
|
692
|
+
if (!currentDocument) {
|
|
693
|
+
throw new Error("No active document found.");
|
|
694
|
+
}
|
|
695
|
+
if (!params.settings || typeof params.settings !== "object") {
|
|
696
|
+
throw new Error("settings object is required for update-settings action");
|
|
697
|
+
}
|
|
698
|
+
await get$e()?.run("document/elements/settings", {
|
|
699
|
+
container: currentDocument.container,
|
|
700
|
+
settings: params.settings,
|
|
701
|
+
options: {
|
|
702
|
+
external: true
|
|
703
|
+
}
|
|
704
|
+
});
|
|
705
|
+
const updatedDocument = getElementor()?.documents?.getCurrent();
|
|
706
|
+
if (!updatedDocument?.container) {
|
|
707
|
+
throw new Error("Document container not found after update");
|
|
708
|
+
}
|
|
709
|
+
validateDocumentSettingsUpdated(updatedDocument.container);
|
|
710
|
+
return {
|
|
711
|
+
content: [
|
|
712
|
+
{
|
|
713
|
+
type: "text",
|
|
714
|
+
text: JSON.stringify({
|
|
715
|
+
success: true,
|
|
716
|
+
message: `Document settings updated successfully. Settings: ${JSON.stringify(
|
|
717
|
+
params.settings,
|
|
718
|
+
null,
|
|
719
|
+
2
|
|
720
|
+
)}`,
|
|
721
|
+
saveChangesSuggestion: 'Suggest the following quick user replies: "Publish Changes", "Save Draft"',
|
|
722
|
+
nextStep: "Page settings updated in editor. User should save the page to persist changes."
|
|
723
|
+
})
|
|
724
|
+
}
|
|
725
|
+
]
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
// src/tools/routes-tool.ts
|
|
730
|
+
import { z as z4 } from "@elementor/schema";
|
|
731
|
+
function addRoutesTool(server) {
|
|
732
|
+
const $e = get$e();
|
|
733
|
+
const routes = $e?.routes?.getAll?.() || [];
|
|
734
|
+
const components = $e?.components?.getAll?.() || [];
|
|
735
|
+
const availableToOpenComponents = components.filter(
|
|
736
|
+
(component) => $e?.components?.get(component)?.getCommands?.()?.open
|
|
737
|
+
);
|
|
738
|
+
const availableToCloseComponents = components.filter(
|
|
739
|
+
(component) => $e?.components?.get(component)?.getCommands?.()?.close
|
|
740
|
+
);
|
|
741
|
+
server.registerTool(
|
|
742
|
+
"routes",
|
|
743
|
+
{
|
|
744
|
+
description: `Manage Elementor editor routing and navigation. Use this tool to open a component, navigate to a route, go back from a route or close components. Always prefer this tool when user is on the Elementor editor. Available routes to navigate to or back from: ${routes.join(", ")}`,
|
|
745
|
+
inputSchema: {
|
|
746
|
+
action: z4.enum(["open", "navigate", "go-back", "close"]),
|
|
747
|
+
route: z4.string().optional().describe(
|
|
748
|
+
"The route to navigate to or back from it. Do not send this parameter if you only want to open a component."
|
|
749
|
+
),
|
|
750
|
+
componentToOpen: z4.enum(availableToOpenComponents.length ? availableToOpenComponents : [""]).optional().describe("The component to open or navigate to."),
|
|
751
|
+
componentToClose: z4.enum(availableToCloseComponents.length ? availableToCloseComponents : [""]).optional().describe("The component to close.")
|
|
752
|
+
},
|
|
753
|
+
annotations: {
|
|
754
|
+
title: "Manage Routes"
|
|
755
|
+
}
|
|
756
|
+
},
|
|
757
|
+
async (params) => {
|
|
758
|
+
switch (params.action) {
|
|
759
|
+
case "open":
|
|
760
|
+
return await handleOpen(params);
|
|
761
|
+
case "navigate":
|
|
762
|
+
return await handleNavigate(params);
|
|
763
|
+
case "go-back":
|
|
764
|
+
return await handleGoBack(params);
|
|
765
|
+
case "close":
|
|
766
|
+
return await handleClose(params);
|
|
767
|
+
default:
|
|
768
|
+
throw new Error(`Unknown action: ${params.action}`);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
);
|
|
772
|
+
}
|
|
773
|
+
async function handleOpen(params) {
|
|
774
|
+
const $e = get$e();
|
|
775
|
+
const component = params.componentToOpen || params.route;
|
|
776
|
+
const openCommand = $e?.components?.get(component)?.getCommands?.()?.open?.registerConfig.command;
|
|
777
|
+
if (openCommand) {
|
|
778
|
+
await $e?.run(openCommand, {});
|
|
779
|
+
} else {
|
|
780
|
+
throw new Error("Could not open component");
|
|
781
|
+
}
|
|
782
|
+
return {
|
|
783
|
+
content: [{ type: "text", text: `Opened: ${component}` }]
|
|
784
|
+
};
|
|
785
|
+
}
|
|
786
|
+
async function handleNavigate(params) {
|
|
787
|
+
const $e = get$e();
|
|
788
|
+
const route = params.route;
|
|
789
|
+
const componentToOpen = params.componentToOpen;
|
|
790
|
+
const openCommand = $e?.components?.get(componentToOpen)?.getCommands?.()?.open?.registerConfig.command;
|
|
791
|
+
if (openCommand) {
|
|
792
|
+
await $e?.run(openCommand, {});
|
|
793
|
+
}
|
|
794
|
+
const routeComponent = $e?.routes?.getComponent?.(route);
|
|
795
|
+
if (routeComponent) {
|
|
796
|
+
$e?.routes?.saveState?.(routeComponent.getNamespace());
|
|
797
|
+
}
|
|
798
|
+
try {
|
|
799
|
+
$e?.routes?.to?.(route, {});
|
|
800
|
+
} catch {
|
|
801
|
+
const openCommandFallback = $e?.components?.get(route)?.getCommands?.()?.open?.registerConfig.command;
|
|
802
|
+
if (openCommandFallback) {
|
|
803
|
+
await $e?.run(openCommandFallback, {});
|
|
804
|
+
} else {
|
|
805
|
+
throw new Error("Could not navigate to route");
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
return {
|
|
809
|
+
content: [{ type: "text", text: `Navigated to: ${route}` }]
|
|
810
|
+
};
|
|
811
|
+
}
|
|
812
|
+
async function handleGoBack(params) {
|
|
813
|
+
const $e = get$e();
|
|
814
|
+
const route = params.route;
|
|
815
|
+
const component = $e?.routes?.getComponent?.(route);
|
|
816
|
+
if (component) {
|
|
817
|
+
$e?.routes?.back?.(component.getNamespace());
|
|
818
|
+
}
|
|
819
|
+
return {
|
|
820
|
+
content: [{ type: "text", text: `Go back to: ${route}` }]
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
async function handleClose(params) {
|
|
824
|
+
const $e = get$e();
|
|
825
|
+
const component = params.componentToClose;
|
|
826
|
+
const closeCommand = $e?.components?.get(component)?.getCommands?.()?.close?.registerConfig.command;
|
|
827
|
+
if (closeCommand) {
|
|
828
|
+
await $e?.run(closeCommand, {});
|
|
829
|
+
} else {
|
|
830
|
+
throw new Error("Could not close component");
|
|
831
|
+
}
|
|
832
|
+
return {
|
|
833
|
+
content: [{ type: "text", text: `Closed: ${component}` }]
|
|
834
|
+
};
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
// src/tools/styling-tool.ts
|
|
838
|
+
import { z as z5 } from "@elementor/schema";
|
|
839
|
+
import { SamplingMessageSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
840
|
+
function addStylingTool(server) {
|
|
841
|
+
server.registerTool(
|
|
842
|
+
"styling",
|
|
843
|
+
{
|
|
844
|
+
description: `This tool provides AI-powered custom CSS styling for Elementor elements. Use this when users want advanced styling that goes beyond Elementor capabilities and can't be targeted using the element settings.
|
|
845
|
+
|
|
846
|
+
**When to use this tool:**
|
|
847
|
+
- Visual effects: shadows, filters, pseudo-elements, advanced selectors
|
|
848
|
+
- Complex animations with custom keyframes or CSS transitions
|
|
849
|
+
- Styling that requires media queries or complex CSS rules
|
|
850
|
+
- Click-triggered effects or other non-motion triggers
|
|
851
|
+
- Custom hover effects that don't involve motion (color changes, opacity, etc.)
|
|
852
|
+
|
|
853
|
+
**When NOT to use this tool:**
|
|
854
|
+
- Basic styling achievable through Elementor settings (colors, typography, spacing, borders, simple hover effects) -> use the elementor__elements with "update-settings" action.
|
|
855
|
+
|
|
856
|
+
**Do NOT use this tool if the user mentions motion effects with supported triggers:**
|
|
857
|
+
- "on hover" with motion (movement, rotation, scaling) \u2192 use motion-effects tool
|
|
858
|
+
- "on scroll" with motion effects \u2192 use motion-effects tool
|
|
859
|
+
- "mouse move" / "follow mouse" \u2192 use motion-effects tool
|
|
860
|
+
- "entrance" / "fade in" / "slide in" animations \u2192 use motion-effects tool
|
|
861
|
+
|
|
862
|
+
**Actions available:**
|
|
863
|
+
- **custom-css**: Generate and apply AI-powered custom CSS to elements
|
|
864
|
+
|
|
865
|
+
This tool generates CSS code using AI, provides preview functionality, and handles user approval workflow for applying custom styles.`,
|
|
866
|
+
inputSchema: {
|
|
867
|
+
action: z5.enum(["custom-css"]).describe(
|
|
868
|
+
"The styling operation to perform. Currently supports custom-css for AI-generated CSS styling."
|
|
869
|
+
),
|
|
870
|
+
elementId: z5.string().describe(
|
|
871
|
+
"The ID of the Elementor element to apply custom styling to. This element will receive the generated CSS code."
|
|
872
|
+
),
|
|
873
|
+
prompt: z5.string().describe(
|
|
874
|
+
"A detailed description of the desired styling. Include specific visual requirements, colors, effects, layout modifications, or any custom styling needs. The more detailed the prompt, the better the generated CSS will match your requirements."
|
|
875
|
+
)
|
|
876
|
+
},
|
|
877
|
+
annotations: {
|
|
878
|
+
title: "Apply Custom Styling"
|
|
879
|
+
}
|
|
880
|
+
},
|
|
881
|
+
async (params) => {
|
|
882
|
+
switch (params.action) {
|
|
883
|
+
case "custom-css":
|
|
884
|
+
return await handleCustomCss(params.elementId, params.prompt, server);
|
|
885
|
+
default:
|
|
886
|
+
throw new Error(`Unknown action: ${params.action}`);
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
);
|
|
890
|
+
}
|
|
891
|
+
async function handleCustomCss(elementId, prompt, server) {
|
|
892
|
+
const container = getElementor()?.getContainer(elementId);
|
|
893
|
+
if (!container) {
|
|
894
|
+
throw new Error(`Element with ID ${elementId} not found.`);
|
|
895
|
+
}
|
|
896
|
+
const htmlMarkup = container.view?.el?.outerHTML || "";
|
|
897
|
+
const parseCSS = (css) => {
|
|
898
|
+
return css && css.replace(/`/g, "").replace(/^css\s*/i, "");
|
|
899
|
+
};
|
|
900
|
+
const samplingCssResult = await server.server.request(
|
|
901
|
+
{
|
|
902
|
+
method: "sampling/createMessage",
|
|
903
|
+
params: {
|
|
904
|
+
messages: [
|
|
905
|
+
{
|
|
906
|
+
role: "user",
|
|
907
|
+
content: {
|
|
908
|
+
type: "text",
|
|
909
|
+
text: prompt
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
],
|
|
913
|
+
maxTokens: 1e3,
|
|
914
|
+
modelPreferences: {
|
|
915
|
+
hints: [
|
|
916
|
+
{
|
|
917
|
+
name: "elementor-css"
|
|
918
|
+
}
|
|
919
|
+
]
|
|
920
|
+
},
|
|
921
|
+
metadata: {
|
|
922
|
+
element_id: elementId,
|
|
923
|
+
html_markup: htmlMarkup
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
},
|
|
927
|
+
SamplingMessageSchema
|
|
928
|
+
);
|
|
929
|
+
const content = samplingCssResult?.content;
|
|
930
|
+
const block = Array.isArray(content) ? content.find((b) => b.type === "text") : content;
|
|
931
|
+
const cssText = block?.type === "text" ? block.text : void 0;
|
|
932
|
+
if (!cssText) {
|
|
933
|
+
throw new Error("Failed to generate CSS: No text content received from API.");
|
|
934
|
+
}
|
|
935
|
+
const parsedCssString = parseCSS(cssText);
|
|
936
|
+
return {
|
|
937
|
+
content: [
|
|
938
|
+
{
|
|
939
|
+
type: "text",
|
|
940
|
+
text: JSON.stringify({
|
|
941
|
+
success: true,
|
|
942
|
+
message: "Custom CSS generated. The CSS needs to be applied through the editor.",
|
|
943
|
+
generatedCss: parsedCssString,
|
|
944
|
+
elementId
|
|
945
|
+
})
|
|
946
|
+
}
|
|
947
|
+
]
|
|
948
|
+
};
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
// src/tools/ui-tool.ts
|
|
952
|
+
import { z as z6 } from "@elementor/schema";
|
|
953
|
+
function addUiTool(server) {
|
|
954
|
+
server.registerTool(
|
|
955
|
+
"ui",
|
|
956
|
+
{
|
|
957
|
+
description: "Manage Elementor editor UI operations. This tool provides control over editor interface actions including responsive preview modes and widget favorites. Use this when you need to: switch between device preview modes (desktop/tablet/mobile), manage favorite widgets, or paste previously copied content. Note: For undo/redo operations, use the page tool instead. The tool interacts directly with the Elementor editor UI and does not modify page content itself.",
|
|
958
|
+
inputSchema: {
|
|
959
|
+
action: z6.enum(["change-device-mode", "toggle-favorite", "ui-paste"]).describe(
|
|
960
|
+
"The UI operation to perform: change-device-mode (switch responsive preview), toggle-favorite (add/remove widget from favorites), ui-paste (paste clipboard content into an existing element)"
|
|
961
|
+
),
|
|
962
|
+
deviceMode: z6.enum(["desktop", "tablet", "mobile"]).optional().describe("Required for change-device-mode. The device mode to switch to for responsive preview"),
|
|
963
|
+
widgetType: z6.string().optional().describe(
|
|
964
|
+
'Required for toggle-favorite. The widget type name (e.g., "heading", "button", "image") to add or remove from favorites'
|
|
965
|
+
),
|
|
966
|
+
elementId: z6.string().optional().describe(
|
|
967
|
+
"Required for ui-paste. The ID of an existing container element where clipboard content should be pasted. Note: The element must exist and content must be in clipboard first"
|
|
968
|
+
)
|
|
969
|
+
},
|
|
970
|
+
annotations: {
|
|
971
|
+
title: "Manage UI"
|
|
972
|
+
}
|
|
973
|
+
},
|
|
974
|
+
async (params) => {
|
|
975
|
+
switch (params.action) {
|
|
976
|
+
case "change-device-mode":
|
|
977
|
+
return await handleChangeDeviceMode(params);
|
|
978
|
+
case "toggle-favorite":
|
|
979
|
+
return await handleToggleFavorite(params);
|
|
980
|
+
case "ui-paste":
|
|
981
|
+
return await handleUiPaste(params);
|
|
982
|
+
default:
|
|
983
|
+
throw new Error(`Unknown action: ${params.action}`);
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
);
|
|
987
|
+
}
|
|
988
|
+
async function handleChangeDeviceMode(params) {
|
|
989
|
+
if (!params.deviceMode) {
|
|
990
|
+
throw new Error("deviceMode is required for change-device-mode action");
|
|
991
|
+
}
|
|
992
|
+
get$e()?.run("panel/change-device-mode", {
|
|
993
|
+
device: params.deviceMode
|
|
994
|
+
});
|
|
995
|
+
return {
|
|
996
|
+
content: [{ type: "text", text: `Device mode changed to ${params.deviceMode}.` }]
|
|
997
|
+
};
|
|
998
|
+
}
|
|
999
|
+
async function handleToggleFavorite(params) {
|
|
1000
|
+
if (!params.widgetType) {
|
|
1001
|
+
throw new Error("widgetType is required for toggle-favorite action");
|
|
1002
|
+
}
|
|
1003
|
+
get$e()?.run("favorites/toggle", {
|
|
1004
|
+
name: params.widgetType
|
|
1005
|
+
});
|
|
1006
|
+
return {
|
|
1007
|
+
content: [{ type: "text", text: `Favorite status toggled for ${params.widgetType}.` }]
|
|
1008
|
+
};
|
|
1009
|
+
}
|
|
1010
|
+
async function handleUiPaste(params) {
|
|
1011
|
+
if (!params.elementId) {
|
|
1012
|
+
throw new Error("elementId is required for ui-paste action");
|
|
1013
|
+
}
|
|
1014
|
+
const container = getElementor()?.getContainer(params.elementId);
|
|
1015
|
+
if (!container) {
|
|
1016
|
+
throw new Error(`Element with ID ${params.elementId} not found.`);
|
|
1017
|
+
}
|
|
1018
|
+
get$e()?.run("document/ui/paste", {
|
|
1019
|
+
container
|
|
1020
|
+
});
|
|
1021
|
+
return {
|
|
1022
|
+
content: [{ type: "text", text: `UI paste performed on element ${params.elementId}.` }]
|
|
1023
|
+
};
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
// src/elementor-mcp-server.ts
|
|
1027
|
+
var SERVER_INSTRUCTIONS = `## Elementor Page Builder
|
|
1028
|
+
|
|
1029
|
+
### Capabilities:
|
|
1030
|
+
**Page Management:**
|
|
1031
|
+
- Manage page settings, saving and routing pages
|
|
1032
|
+
- Control the editor UI, including switching between desktop, tablet, and mobile views
|
|
1033
|
+
|
|
1034
|
+
**Global Styles:**
|
|
1035
|
+
- Work with global styles, helping manage shared design settings like colors and fonts across the site
|
|
1036
|
+
|
|
1037
|
+
**AI-Powered Content Creation:**
|
|
1038
|
+
- Generate and edit text and insert it into the page
|
|
1039
|
+
- Generate images and place them on the canvas
|
|
1040
|
+
|
|
1041
|
+
**Custom Styling & Code:**
|
|
1042
|
+
- Apply custom CSS to elements
|
|
1043
|
+
- Generate supported code snippets
|
|
1044
|
+
|
|
1045
|
+
### Limitations:
|
|
1046
|
+
**Element Management (Not Supported):**
|
|
1047
|
+
- Cannot create or edit individual Elementor elements such as widgets or containers
|
|
1048
|
+
- Cannot build page layouts or create containers
|
|
1049
|
+
- Cannot modify widget-level settings
|
|
1050
|
+
- Cannot apply motion effects
|
|
1051
|
+
- Cannot reorder sections or perform detailed canvas-level edits
|
|
1052
|
+
- Cannot create fully designed or polished pages
|
|
1053
|
+
- Cannot fully resolve responsiveness issues
|
|
1054
|
+
- Support for these editor-level capabilities is planned for Editor V4
|
|
1055
|
+
|
|
1056
|
+
**Theme Builder:**
|
|
1057
|
+
- Cannot create or manage Theme Builder templates, including headers, footers, single posts, archives, products, loop items, or 404 pages
|
|
1058
|
+
- Cannot set display conditions for templates
|
|
1059
|
+
- Cannot configure popup triggers and advanced rules
|
|
1060
|
+
|
|
1061
|
+
**System Settings:**
|
|
1062
|
+
- Cannot change Elementor system-level settings
|
|
1063
|
+
- Cannot activate or work with Editor V4
|
|
1064
|
+
- Cannot manage form submissions
|
|
1065
|
+
- Cannot add custom fonts or icons
|
|
1066
|
+
- Cannot manage user roles
|
|
1067
|
+
- Cannot roll back Elementor versions
|
|
1068
|
+
- Cannot place the site in maintenance mode
|
|
1069
|
+
- Cannot export the website
|
|
1070
|
+
- Cannot apply full website templates
|
|
1071
|
+
|
|
1072
|
+
**Code & Widgets:**
|
|
1073
|
+
- Cannot register PHP code or create new custom widgets, though Angie may provide guidance, code snippets, or plugin suggestions where helpful
|
|
1074
|
+
|
|
1075
|
+
**Note**: While page names can include terms like "header" or "footer", these won't function as actual theme parts without Theme Builder access.
|
|
1076
|
+
|
|
1077
|
+
**Important**: When users ask "What can Angie do?" or similar questions about Angie's general capabilities, use the \`what-can-angie-do\` tool from the knowledge MCP server instead of generating your own response.`;
|
|
1078
|
+
var VERSION = "2.0.0";
|
|
1079
|
+
async function createElementorServer() {
|
|
1080
|
+
await waitForElementorEditor();
|
|
1081
|
+
const server = new McpServer(
|
|
1082
|
+
{
|
|
1083
|
+
name: "elementor-server",
|
|
1084
|
+
version: VERSION,
|
|
1085
|
+
title: "Elementor"
|
|
1086
|
+
},
|
|
1087
|
+
{
|
|
1088
|
+
instructions: SERVER_INSTRUCTIONS,
|
|
1089
|
+
capabilities: {
|
|
1090
|
+
resources: {
|
|
1091
|
+
subscribe: true
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
);
|
|
1096
|
+
addElementorResources(server);
|
|
1097
|
+
addPageTool(server);
|
|
1098
|
+
addUiTool(server);
|
|
1099
|
+
addDynamicTool(server);
|
|
1100
|
+
addRoutesTool(server);
|
|
1101
|
+
addAiTool(server);
|
|
1102
|
+
addStylingTool(server);
|
|
1103
|
+
const sdk = getAngieSdk();
|
|
1104
|
+
await sdk.waitForReady();
|
|
1105
|
+
sdk.registerLocalServer({ server, version: VERSION, description: SERVER_INSTRUCTIONS, name: "Elementor" });
|
|
1106
|
+
return server;
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
// src/init.ts
|
|
1110
|
+
function init() {
|
|
1111
|
+
createElementorServer();
|
|
1112
|
+
}
|
|
1113
|
+
export {
|
|
1114
|
+
RESOURCE_NAME_ELEMENT_SETTINGS,
|
|
1115
|
+
RESOURCE_NAME_PAGE_OVERVIEW,
|
|
1116
|
+
RESOURCE_NAME_PAGE_SETTINGS,
|
|
1117
|
+
RESOURCE_NAME_WIDGET_CONFIG,
|
|
1118
|
+
RESOURCE_URI_ELEMENT_SETTINGS_TEMPLATE,
|
|
1119
|
+
RESOURCE_URI_PAGE_OVERVIEW,
|
|
1120
|
+
RESOURCE_URI_PAGE_SETTINGS,
|
|
1121
|
+
RESOURCE_URI_WIDGET_CONFIG_TEMPLATE,
|
|
1122
|
+
createElementorServer,
|
|
1123
|
+
init
|
|
1124
|
+
};
|
|
1125
|
+
//# sourceMappingURL=index.mjs.map
|