// Elementor MCP Tools
import { ElementorService } from '../services/elementor/elementor-service.js';
import { logger } from '../utils/logger.js';
export function createElementorTools(elementorService: ElementorService) {
const tools: any[] = [];
// ========== BASIC ELEMENTOR OPERATIONS ==========
tools.push({
name: 'get_elementor_data',
description: 'Get complete Elementor data for a page',
inputSchema: {
type: 'object',
properties: {
post_id: {
type: 'number',
description: 'Post/Page ID'
}
},
required: ['post_id']
},
handler: async (args: any) => {
const result = await elementorService.data.getElementorData(args.post_id);
return {
content: [{
type: 'text',
text: JSON.stringify(result, null, 2)
}]
};
}
});
tools.push({
name: 'update_elementor_data',
description: 'Update complete Elementor data for a page',
inputSchema: {
type: 'object',
properties: {
post_id: {
type: 'number',
description: 'Post/Page ID'
},
elementor_data: {
type: 'string',
description: 'Elementor data as JSON string'
}
},
required: ['post_id', 'elementor_data']
},
handler: async (args: any) => {
const data = JSON.parse(args.elementor_data);
await elementorService.data.updateElementorData(args.post_id, data);
return {
content: [{
type: 'text',
text: `Elementor data updated successfully for post ${args.post_id}`
}]
};
}
});
tools.push({
name: 'get_elementor_data_chunked',
description: 'Get Elementor data in manageable chunks for large pages',
inputSchema: {
type: 'object',
properties: {
post_id: {
type: 'number',
description: 'Post/Page ID'
},
chunk_size: {
type: 'number',
description: 'Number of elements per chunk (default: 50)',
default: 50
}
},
required: ['post_id']
},
handler: async (args: any) => {
const result = await elementorService.data.getElementorDataChunked(
args.post_id,
args.chunk_size || 50
);
return {
content: [{
type: 'text',
text: JSON.stringify(result, null, 2)
}]
};
}
});
tools.push({
name: 'backup_elementor_data',
description: 'Create backup of Elementor data before making changes',
inputSchema: {
type: 'object',
properties: {
post_id: {
type: 'number',
description: 'Post/Page ID'
}
},
required: ['post_id']
},
handler: async (args: any) => {
const result = await elementorService.data.backupElementorData(args.post_id);
return {
content: [{
type: 'text',
text: JSON.stringify(result, null, 2)
}]
};
}
});
tools.push({
name: 'clear_elementor_cache',
description: 'Clear Elementor cache for better performance',
inputSchema: {
type: 'object',
properties: {}
},
handler: async (args: any) => {
await elementorService.data.clearElementorCache();
return {
content: [{
type: 'text',
text: 'Elementor cache cleared successfully'
}]
};
}
});
tools.push({
name: 'get_page_structure',
description: 'Get the hierarchical structure of a page',
inputSchema: {
type: 'object',
properties: {
post_id: {
type: 'number',
description: 'Post/Page ID'
},
include_settings: {
type: 'boolean',
description: 'Include element settings (default: false)',
default: false
}
},
required: ['post_id']
},
handler: async (args: any) => {
const data = await elementorService.data.getElementorData(args.post_id);
const structure = elementorService.manipulation.getPageStructure(
data.elements,
args.include_settings || false
);
return {
content: [{
type: 'text',
text: JSON.stringify(structure, null, 2)
}]
};
}
});
// ========== SECTION & CONTAINER CREATION ==========
tools.push({
name: 'create_elementor_section',
description: 'Create a new Elementor section with specified columns',
inputSchema: {
type: 'object',
properties: {
post_id: {
type: 'number',
description: 'Post/Page ID'
},
columns: {
type: 'number',
description: 'Number of columns (default: 1)',
default: 1
},
position: {
type: 'number',
description: 'Position to insert section (optional)'
},
section_settings: {
type: 'object',
description: 'Section settings (background, layout, etc.)'
}
},
required: ['post_id']
},
handler: async (args: any) => {
const data = await elementorService.data.getElementorData(args.post_id);
const newSection = elementorService.builder.createSection(
args.columns || 1,
args.section_settings || {}
);
if (args.position !== undefined) {
data.elements.splice(args.position, 0, newSection);
} else {
data.elements.push(newSection);
}
await elementorService.data.updateElementorData(args.post_id, data);
return {
content: [{
type: 'text',
text: `Section created successfully with ID: ${newSection.id}`
}]
};
}
});
tools.push({
name: 'create_elementor_container',
description: 'Create a new Elementor container (Flexbox)',
inputSchema: {
type: 'object',
properties: {
post_id: {
type: 'number',
description: 'Post/Page ID'
},
position: {
type: 'number',
description: 'Position to insert container (optional)'
},
container_settings: {
type: 'object',
description: 'Container settings'
}
},
required: ['post_id']
},
handler: async (args: any) => {
const data = await elementorService.data.getElementorData(args.post_id);
const newContainer = elementorService.builder.createContainer(
args.container_settings || {}
);
if (args.position !== undefined) {
data.elements.splice(args.position, 0, newContainer);
} else {
data.elements.push(newContainer);
}
await elementorService.data.updateElementorData(args.post_id, data);
return {
content: [{
type: 'text',
text: `Container created successfully with ID: ${newContainer.id}`
}]
};
}
});
// ========== WIDGET MANAGEMENT ==========
tools.push({
name: 'add_widget_to_section',
description: 'Add a widget to an Elementor section or column',
inputSchema: {
type: 'object',
properties: {
post_id: {
type: 'number',
description: 'Post/Page ID'
},
parent_id: {
type: 'string',
description: 'Section or column ID to add widget to'
},
widget_type: {
type: 'string',
description: 'Widget type (heading, text-editor, button, image, etc.)'
},
widget_settings: {
type: 'object',
description: 'Widget settings'
},
position: {
type: 'number',
description: 'Position within parent (optional)'
}
},
required: ['post_id', 'parent_id', 'widget_type']
},
handler: async (args: any) => {
const data = await elementorService.data.getElementorData(args.post_id);
const widget = elementorService.builder.createWidget(
args.widget_type,
args.widget_settings || {}
);
const { element: parent } = elementorService.manipulation.findElementById(
data.elements,
args.parent_id
);
if (!parent || !parent.elements) {
throw new Error(`Parent element ${args.parent_id} not found or cannot contain widgets`);
}
if (args.position !== undefined) {
parent.elements.splice(args.position, 0, widget);
} else {
parent.elements.push(widget);
}
await elementorService.data.updateElementorData(args.post_id, data);
return {
content: [{
type: 'text',
text: `Widget ${args.widget_type} added successfully with ID: ${widget.id}`
}]
};
}
});
tools.push({
name: 'clone_widget',
description: 'Clone an existing widget',
inputSchema: {
type: 'object',
properties: {
post_id: {
type: 'number',
description: 'Post/Page ID'
},
widget_id: {
type: 'string',
description: 'Widget ID to clone'
},
target_parent_id: {
type: 'string',
description: 'Target parent ID (optional, uses same parent if not specified)'
},
position: {
type: 'number',
description: 'Position in target parent (optional)'
}
},
required: ['post_id', 'widget_id']
},
handler: async (args: any) => {
const data = await elementorService.data.getElementorData(args.post_id);
const { element: widget, parent } = elementorService.manipulation.findElementById(
data.elements,
args.widget_id
);
if (!widget) {
throw new Error(`Widget ${args.widget_id} not found`);
}
const clonedWidget = elementorService.manipulation.cloneElement(widget);
const targetParentId = args.target_parent_id || parent?.id;
if (targetParentId) {
const { element: targetParent } = elementorService.manipulation.findElementById(
data.elements,
targetParentId
);
if (targetParent && targetParent.elements) {
if (args.position !== undefined) {
targetParent.elements.splice(args.position, 0, clonedWidget);
} else {
targetParent.elements.push(clonedWidget);
}
}
}
await elementorService.data.updateElementorData(args.post_id, data);
return {
content: [{
type: 'text',
text: `Widget cloned successfully. New ID: ${clonedWidget.id}`
}]
};
}
});
// ========== ELEMENT MANAGEMENT ==========
tools.push({
name: 'update_element_settings',
description: 'Update settings for any Elementor element',
inputSchema: {
type: 'object',
properties: {
post_id: {
type: 'number',
description: 'Post/Page ID'
},
element_id: {
type: 'string',
description: 'Element ID'
},
settings: {
type: 'object',
description: 'Settings to update'
}
},
required: ['post_id', 'element_id', 'settings']
},
handler: async (args: any) => {
const data = await elementorService.data.getElementorData(args.post_id);
const updated = elementorService.manipulation.updateElement(
data.elements,
args.element_id,
args.settings
);
if (!updated) {
throw new Error(`Element ${args.element_id} not found`);
}
await elementorService.data.updateElementorData(args.post_id, data);
return {
content: [{
type: 'text',
text: `Element ${args.element_id} updated successfully`
}]
};
}
});
tools.push({
name: 'delete_elementor_element',
description: 'Delete an Elementor element (section, column, or widget)',
inputSchema: {
type: 'object',
properties: {
post_id: {
type: 'number',
description: 'Post/Page ID'
},
element_id: {
type: 'string',
description: 'Element ID to delete'
}
},
required: ['post_id', 'element_id']
},
handler: async (args: any) => {
const data = await elementorService.data.getElementorData(args.post_id);
const deleted = elementorService.manipulation.deleteElement(
data.elements,
args.element_id
);
if (!deleted) {
throw new Error(`Element ${args.element_id} not found`);
}
await elementorService.data.updateElementorData(args.post_id, data);
return {
content: [{
type: 'text',
text: `Element ${args.element_id} deleted successfully`
}]
};
}
});
tools.push({
name: 'move_element',
description: 'Move an element to a new position',
inputSchema: {
type: 'object',
properties: {
post_id: {
type: 'number',
description: 'Post/Page ID'
},
element_id: {
type: 'string',
description: 'Element ID to move'
},
target_parent_id: {
type: 'string',
description: 'Target parent ID (null for root level)'
},
position: {
type: 'number',
description: 'Position in target parent'
}
},
required: ['post_id', 'element_id', 'position']
},
handler: async (args: any) => {
const data = await elementorService.data.getElementorData(args.post_id);
const moved = elementorService.manipulation.moveElement(
data.elements,
args.element_id,
args.target_parent_id || null,
args.position
);
if (!moved) {
throw new Error(`Failed to move element ${args.element_id}`);
}
await elementorService.data.updateElementorData(args.post_id, data);
return {
content: [{
type: 'text',
text: `Element ${args.element_id} moved successfully`
}]
};
}
});
tools.push({
name: 'reorder_elements',
description: 'Change the order of elements within a parent',
inputSchema: {
type: 'object',
properties: {
post_id: {
type: 'number',
description: 'Post/Page ID'
},
parent_id: {
type: 'string',
description: 'Parent element ID (null for root level)'
},
new_order: {
type: 'array',
items: { type: 'string' },
description: 'Array of element IDs in new order'
}
},
required: ['post_id', 'new_order']
},
handler: async (args: any) => {
const data = await elementorService.data.getElementorData(args.post_id);
const reordered = elementorService.manipulation.reorderElements(
data.elements,
args.parent_id || null,
args.new_order
);
if (!reordered) {
throw new Error('Failed to reorder elements');
}
await elementorService.data.updateElementorData(args.post_id, data);
return {
content: [{
type: 'text',
text: `Elements reordered successfully`
}]
};
}
});
// ========== TEMPLATE MANAGEMENT ==========
tools.push({
name: 'get_elementor_templates',
description: 'Get Elementor templates',
inputSchema: {
type: 'object',
properties: {
per_page: {
type: 'number',
description: 'Number of templates to retrieve (default: 10)',
default: 10
},
type: {
type: 'string',
description: 'Template type (page, section, widget)'
}
}
},
handler: async (args: any) => {
const result = await elementorService.templates.getTemplates(
args.per_page || 10,
args.type
);
return {
content: [{
type: 'text',
text: JSON.stringify(result, null, 2)
}]
};
}
});
tools.push({
name: 'create_elementor_template',
description: 'Create a new Elementor template',
inputSchema: {
type: 'object',
properties: {
title: {
type: 'string',
description: 'Template title'
},
type: {
type: 'string',
description: 'Template type (page, section, widget)'
},
content: {
type: 'string',
description: 'Template content as JSON string'
}
},
required: ['title', 'type', 'content']
},
handler: async (args: any) => {
const result = await elementorService.templates.createTemplate(
args.title,
args.type,
args.content,
{}
);
return {
content: [{
type: 'text',
text: `Template created successfully with ID: ${result.id}`
}]
};
}
});
tools.push({
name: 'apply_template_to_page',
description: 'Apply an Elementor template to a page',
inputSchema: {
type: 'object',
properties: {
page_id: {
type: 'number',
description: 'Page ID to apply template to'
},
template_id: {
type: 'number',
description: 'Template ID to apply'
}
},
required: ['page_id', 'template_id']
},
handler: async (args: any) => {
await elementorService.templates.applyTemplateToPage(
args.page_id,
args.template_id
);
return {
content: [{
type: 'text',
text: `Template ${args.template_id} applied successfully to page ${args.page_id}`
}]
};
}
});
// ========== PRE-BUILT WIDGET CREATORS ==========
tools.push({
name: 'add_heading_widget',
description: 'Add a heading widget to a section or column',
inputSchema: {
type: 'object',
properties: {
post_id: {
type: 'number',
description: 'Post/Page ID'
},
parent_id: {
type: 'string',
description: 'Parent element ID (section or column)'
},
text: {
type: 'string',
description: 'Heading text'
},
tag: {
type: 'string',
description: 'HTML tag (h1, h2, h3, h4, h5, h6)',
default: 'h2'
},
position: {
type: 'number',
description: 'Position within parent (optional)'
}
},
required: ['post_id', 'parent_id', 'text']
},
handler: async (args: any) => {
const data = await elementorService.data.getElementorData(args.post_id);
const widget = elementorService.builder.createHeadingWidget(args.text, args.tag || 'h2');
const { element: parent } = elementorService.manipulation.findElementById(
data.elements,
args.parent_id
);
if (!parent || !parent.elements) {
throw new Error(`Parent ${args.parent_id} not found`);
}
if (args.position !== undefined) {
parent.elements.splice(args.position, 0, widget);
} else {
parent.elements.push(widget);
}
await elementorService.data.updateElementorData(args.post_id, data);
return {
content: [{
type: 'text',
text: `Heading widget added successfully with ID: ${widget.id}`
}]
};
}
});
tools.push({
name: 'add_text_widget',
description: 'Add a text editor widget',
inputSchema: {
type: 'object',
properties: {
post_id: {
type: 'number',
description: 'Post/Page ID'
},
parent_id: {
type: 'string',
description: 'Parent element ID'
},
content: {
type: 'string',
description: 'Text content (HTML supported)'
},
position: {
type: 'number',
description: 'Position within parent (optional)'
}
},
required: ['post_id', 'parent_id', 'content']
},
handler: async (args: any) => {
const data = await elementorService.data.getElementorData(args.post_id);
const widget = elementorService.builder.createTextWidget(args.content);
const { element: parent } = elementorService.manipulation.findElementById(
data.elements,
args.parent_id
);
if (!parent || !parent.elements) {
throw new Error(`Parent ${args.parent_id} not found`);
}
if (args.position !== undefined) {
parent.elements.splice(args.position, 0, widget);
} else {
parent.elements.push(widget);
}
await elementorService.data.updateElementorData(args.post_id, data);
return {
content: [{
type: 'text',
text: `Text widget added successfully with ID: ${widget.id}`
}]
};
}
});
tools.push({
name: 'add_button_widget',
description: 'Add a button widget',
inputSchema: {
type: 'object',
properties: {
post_id: {
type: 'number',
description: 'Post/Page ID'
},
parent_id: {
type: 'string',
description: 'Parent element ID'
},
text: {
type: 'string',
description: 'Button text'
},
link: {
type: 'string',
description: 'Button link URL',
default: '#'
},
position: {
type: 'number',
description: 'Position within parent (optional)'
}
},
required: ['post_id', 'parent_id', 'text']
},
handler: async (args: any) => {
const data = await elementorService.data.getElementorData(args.post_id);
const widget = elementorService.builder.createButtonWidget(args.text, args.link || '#');
const { element: parent } = elementorService.manipulation.findElementById(
data.elements,
args.parent_id
);
if (!parent || !parent.elements) {
throw new Error(`Parent ${args.parent_id} not found`);
}
if (args.position !== undefined) {
parent.elements.splice(args.position, 0, widget);
} else {
parent.elements.push(widget);
}
await elementorService.data.updateElementorData(args.post_id, data);
return {
content: [{
type: 'text',
text: `Button widget added successfully with ID: ${widget.id}`
}]
};
}
});
tools.push({
name: 'add_image_widget',
description: 'Add an image widget',
inputSchema: {
type: 'object',
properties: {
post_id: {
type: 'number',
description: 'Post/Page ID'
},
parent_id: {
type: 'string',
description: 'Parent element ID'
},
image_url: {
type: 'string',
description: 'Image URL'
},
image_id: {
type: 'number',
description: 'WordPress media ID (optional)'
},
position: {
type: 'number',
description: 'Position within parent (optional)'
}
},
required: ['post_id', 'parent_id', 'image_url']
},
handler: async (args: any) => {
const data = await elementorService.data.getElementorData(args.post_id);
const widget = elementorService.builder.createImageWidget(args.image_url, args.image_id);
const { element: parent } = elementorService.manipulation.findElementById(
data.elements,
args.parent_id
);
if (!parent || !parent.elements) {
throw new Error(`Parent ${args.parent_id} not found`);
}
if (args.position !== undefined) {
parent.elements.splice(args.position, 0, widget);
} else {
parent.elements.push(widget);
}
await elementorService.data.updateElementorData(args.post_id, data);
return {
content: [{
type: 'text',
text: `Image widget added successfully with ID: ${widget.id}`
}]
};
}
});
// ========== ELEMENT SEARCH ==========
tools.push({
name: 'find_elements_by_type',
description: 'Find all elements of a specific type in a page',
inputSchema: {
type: 'object',
properties: {
post_id: {
type: 'number',
description: 'Post/Page ID'
},
element_type: {
type: 'string',
description: 'Element type (section, column, widget, container)'
}
},
required: ['post_id', 'element_type']
},
handler: async (args: any) => {
const data = await elementorService.data.getElementorData(args.post_id);
const elements = elementorService.manipulation.findElementsByType(
data.elements,
args.element_type
);
return {
content: [{
type: 'text',
text: JSON.stringify(elements.map(el => ({
id: el.id,
type: el.elType,
widgetType: el.widgetType
})), null, 2)
}]
};
}
});
tools.push({
name: 'find_widgets_by_type',
description: 'Find all widgets of a specific type',
inputSchema: {
type: 'object',
properties: {
post_id: {
type: 'number',
description: 'Post/Page ID'
},
widget_type: {
type: 'string',
description: 'Widget type (heading, text-editor, button, etc.)'
}
},
required: ['post_id', 'widget_type']
},
handler: async (args: any) => {
const data = await elementorService.data.getElementorData(args.post_id);
const widgets = elementorService.manipulation.findWidgetsByType(
data.elements,
args.widget_type
);
return {
content: [{
type: 'text',
text: JSON.stringify(widgets.map(w => ({
id: w.id,
widgetType: w.widgetType,
settings: w.settings
})), null, 2)
}]
};
}
});
// ========== COPY & DUPLICATE ==========
tools.push({
name: 'copy_element_settings',
description: 'Copy settings from one element to another',
inputSchema: {
type: 'object',
properties: {
post_id: {
type: 'number',
description: 'Post/Page ID'
},
source_element_id: {
type: 'string',
description: 'Source element ID'
},
target_element_id: {
type: 'string',
description: 'Target element ID'
},
settings_keys: {
type: 'array',
items: { type: 'string' },
description: 'Specific settings to copy (optional, copies all if not specified)'
}
},
required: ['post_id', 'source_element_id', 'target_element_id']
},
handler: async (args: any) => {
const data = await elementorService.data.getElementorData(args.post_id);
const copied = elementorService.manipulation.copyElementSettings(
data.elements,
args.source_element_id,
args.target_element_id,
args.settings_keys
);
if (!copied) {
throw new Error('Failed to copy element settings');
}
await elementorService.data.updateElementorData(args.post_id, data);
return {
content: [{
type: 'text',
text: 'Element settings copied successfully'
}]
};
}
});
tools.push({
name: 'duplicate_section',
description: 'Duplicate an existing section',
inputSchema: {
type: 'object',
properties: {
post_id: {
type: 'number',
description: 'Post/Page ID'
},
section_id: {
type: 'string',
description: 'Section ID to duplicate'
},
position: {
type: 'number',
description: 'Position to insert duplicated section (optional)'
}
},
required: ['post_id', 'section_id']
},
handler: async (args: any) => {
const data = await elementorService.data.getElementorData(args.post_id);
const { element: section } = elementorService.manipulation.findElementById(
data.elements,
args.section_id
);
if (!section) {
throw new Error(`Section ${args.section_id} not found`);
}
const duplicated = elementorService.manipulation.cloneElement(section) as any;
if (args.position !== undefined) {
data.elements.splice(args.position, 0, duplicated);
} else {
data.elements.push(duplicated);
}
await elementorService.data.updateElementorData(args.post_id, data);
return {
content: [{
type: 'text',
text: `Section duplicated successfully. New ID: ${duplicated.id}`
}]
};
}
});
return tools;
}