@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.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