@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/src/resources.ts
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { type McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
|
|
3
|
+
import { getPageOverView, loadDocumentSchema, loadDocumentSettings } from './context';
|
|
4
|
+
import type { ElementorControls } from './types';
|
|
5
|
+
import { encodeToolJson, getElementor } from './utils';
|
|
6
|
+
|
|
7
|
+
export const RESOURCE_NAME_ELEMENT_SETTINGS = 'elementor-element-settings';
|
|
8
|
+
export const RESOURCE_URI_ELEMENT_SETTINGS_TEMPLATE = 'elementor://editor/element-settings/{elementId}';
|
|
9
|
+
|
|
10
|
+
export const RESOURCE_NAME_WIDGET_CONFIG = 'elementor-widget-config';
|
|
11
|
+
export const RESOURCE_URI_WIDGET_CONFIG_TEMPLATE = 'elementor://editor/widget-config/{widgetType}';
|
|
12
|
+
|
|
13
|
+
export const RESOURCE_NAME_PAGE_OVERVIEW = 'elementor-page-overview';
|
|
14
|
+
export const RESOURCE_URI_PAGE_OVERVIEW = 'elementor://editor/page-overview';
|
|
15
|
+
|
|
16
|
+
export const RESOURCE_NAME_PAGE_SETTINGS = 'elementor-page-settings';
|
|
17
|
+
export const RESOURCE_URI_PAGE_SETTINGS = 'elementor://editor/page-settings';
|
|
18
|
+
|
|
19
|
+
export function decodeResourceVariable( value: string ): string {
|
|
20
|
+
try {
|
|
21
|
+
let decoded = decodeURIComponent( value ).replace( /[·]/g, '' );
|
|
22
|
+
decoded = decoded.replace( /^{|}$/g, '' );
|
|
23
|
+
return decoded;
|
|
24
|
+
} catch {
|
|
25
|
+
return value;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function handleGetWidgetSettings( params: { elementId: string; action: string } ): Promise< {
|
|
30
|
+
content: [ { type: 'text'; text: string } ];
|
|
31
|
+
} > {
|
|
32
|
+
const elementor = getElementor();
|
|
33
|
+
const container = elementor?.getContainer( params.elementId );
|
|
34
|
+
if ( ! container ) {
|
|
35
|
+
throw new Error( `Element with ID ${ params.elementId } not found.` );
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const settings = container.settings.attributes || {};
|
|
39
|
+
return {
|
|
40
|
+
content: [ { type: 'text', text: encodeToolJson( settings ) } ],
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function handleGetWidgetSchema( params: { widgetType: string; action: string } ): Promise< {
|
|
45
|
+
content: [ { type: 'text'; text: string } ];
|
|
46
|
+
} > {
|
|
47
|
+
const elementor = getElementor();
|
|
48
|
+
const controls = elementor?.widgetsCache[ params.widgetType ]?.controls as ElementorControls | undefined;
|
|
49
|
+
if ( ! controls ) {
|
|
50
|
+
throw new Error( `Widget type ${ params.widgetType } not found.` );
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
content: [ { type: 'text', text: encodeToolJson( controls ) } ],
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function addElementorResources( server: McpServer ): void {
|
|
59
|
+
server.resource(
|
|
60
|
+
RESOURCE_NAME_PAGE_OVERVIEW,
|
|
61
|
+
RESOURCE_URI_PAGE_OVERVIEW,
|
|
62
|
+
{
|
|
63
|
+
description:
|
|
64
|
+
'Complete page structure showing all elements, containers, and their hierarchical relationships on the current Elementor page',
|
|
65
|
+
},
|
|
66
|
+
async ( uri ) => {
|
|
67
|
+
const overview = getPageOverView();
|
|
68
|
+
|
|
69
|
+
if ( ! overview || overview.error ) {
|
|
70
|
+
throw new Error( overview?.error || 'Failed to retrieve page overview' );
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
contents: [
|
|
75
|
+
{
|
|
76
|
+
uri: uri.toString(),
|
|
77
|
+
mimeType: 'application/json',
|
|
78
|
+
text: encodeToolJson( overview ),
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
server.resource(
|
|
86
|
+
RESOURCE_NAME_PAGE_SETTINGS,
|
|
87
|
+
RESOURCE_URI_PAGE_SETTINGS,
|
|
88
|
+
{
|
|
89
|
+
description:
|
|
90
|
+
'Page/document settings schema and current values including title, template, margins, padding, and backgrounds',
|
|
91
|
+
},
|
|
92
|
+
async ( uri ) => {
|
|
93
|
+
const elementor = getElementor();
|
|
94
|
+
const currentDocument = elementor?.documents?.getCurrent();
|
|
95
|
+
if ( ! currentDocument ) {
|
|
96
|
+
throw new Error( 'No active document found' );
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const documentSchema = loadDocumentSchema( currentDocument.id );
|
|
100
|
+
const documentSettings = loadDocumentSettings( currentDocument.id );
|
|
101
|
+
|
|
102
|
+
if ( ! documentSchema || ! documentSettings ) {
|
|
103
|
+
throw new Error( 'Failed to retrieve page settings' );
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const result = {
|
|
107
|
+
schema: documentSchema,
|
|
108
|
+
currentSettings: documentSettings,
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
contents: [
|
|
113
|
+
{
|
|
114
|
+
uri: uri.toString(),
|
|
115
|
+
mimeType: 'application/json',
|
|
116
|
+
text: encodeToolJson( result ),
|
|
117
|
+
},
|
|
118
|
+
],
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
server.resource(
|
|
124
|
+
RESOURCE_NAME_ELEMENT_SETTINGS,
|
|
125
|
+
new ResourceTemplate( RESOURCE_URI_ELEMENT_SETTINGS_TEMPLATE, {
|
|
126
|
+
list: undefined,
|
|
127
|
+
} ),
|
|
128
|
+
{
|
|
129
|
+
description:
|
|
130
|
+
'Complete settings schema and current values for a specific element, including all available configuration options and their current state',
|
|
131
|
+
},
|
|
132
|
+
async ( uri, variables ) => {
|
|
133
|
+
let elementId = Array.isArray( variables.elementId ) ? variables.elementId[ 0 ] : variables.elementId;
|
|
134
|
+
|
|
135
|
+
if ( ! elementId ) {
|
|
136
|
+
throw new Error( 'Element ID is required' );
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
elementId = decodeResourceVariable( elementId );
|
|
140
|
+
|
|
141
|
+
const result = await handleGetWidgetSettings( { elementId, action: 'get-widget-settings' } );
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
contents: [
|
|
145
|
+
{
|
|
146
|
+
uri: uri.toString(),
|
|
147
|
+
mimeType: 'text/plain',
|
|
148
|
+
text: result.content[ 0 ].text,
|
|
149
|
+
},
|
|
150
|
+
],
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
server.resource(
|
|
156
|
+
RESOURCE_NAME_WIDGET_CONFIG,
|
|
157
|
+
new ResourceTemplate( RESOURCE_URI_WIDGET_CONFIG_TEMPLATE, {
|
|
158
|
+
list: undefined,
|
|
159
|
+
} ),
|
|
160
|
+
{
|
|
161
|
+
description:
|
|
162
|
+
'Complete configuration schema for a specific widget type, showing all available settings, properties, and their expected formats',
|
|
163
|
+
},
|
|
164
|
+
async ( uri, variables ) => {
|
|
165
|
+
let widgetType = Array.isArray( variables.widgetType ) ? variables.widgetType[ 0 ] : variables.widgetType;
|
|
166
|
+
|
|
167
|
+
if ( ! widgetType ) {
|
|
168
|
+
throw new Error( 'Widget type is required' );
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
widgetType = decodeResourceVariable( widgetType );
|
|
172
|
+
|
|
173
|
+
const result = await handleGetWidgetSchema( { widgetType, action: 'get-widget-schema' } );
|
|
174
|
+
|
|
175
|
+
return {
|
|
176
|
+
contents: [
|
|
177
|
+
{
|
|
178
|
+
uri: uri.toString(),
|
|
179
|
+
mimeType: 'text/plain',
|
|
180
|
+
text: result.content[ 0 ].text,
|
|
181
|
+
},
|
|
182
|
+
],
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
);
|
|
186
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { z } from '@elementor/schema';
|
|
2
|
+
import { type McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
|
+
|
|
4
|
+
import type { McpToolResult } from '../types';
|
|
5
|
+
import { get$e } from '../utils';
|
|
6
|
+
|
|
7
|
+
export function addAiTool( server: McpServer ): void {
|
|
8
|
+
server.registerTool(
|
|
9
|
+
'ai',
|
|
10
|
+
{
|
|
11
|
+
description: 'Manage Elementor AI integration features and interfaces.',
|
|
12
|
+
inputSchema: {
|
|
13
|
+
action: z
|
|
14
|
+
.enum( [ 'open-brand-voice', 'open-choose-element', 'open-text-to-elementor' ] )
|
|
15
|
+
.describe( 'The AI operation to perform' ),
|
|
16
|
+
},
|
|
17
|
+
annotations: {
|
|
18
|
+
title: 'Manage AI Integration',
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
async ( params ) => {
|
|
22
|
+
switch ( params.action ) {
|
|
23
|
+
case 'open-brand-voice':
|
|
24
|
+
return await handleOpenBrandVoice();
|
|
25
|
+
case 'open-choose-element':
|
|
26
|
+
return await handleOpenChooseElement();
|
|
27
|
+
case 'open-text-to-elementor':
|
|
28
|
+
return await handleOpenTextToElementor();
|
|
29
|
+
default:
|
|
30
|
+
throw new Error( `Unknown action: ${ params.action }` );
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function handleOpenBrandVoice(): Promise< McpToolResult > {
|
|
37
|
+
await get$e()?.run( 'ai-integration/open-brand-voice' );
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
content: [ { type: 'text', text: 'Brand Voice interface opened.' } ],
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function handleOpenChooseElement(): Promise< McpToolResult > {
|
|
45
|
+
await get$e()?.run( 'ai-integration/open-choose-element' );
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
content: [ { type: 'text', text: 'Choose Element interface opened.' } ],
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function handleOpenTextToElementor(): Promise< McpToolResult > {
|
|
53
|
+
await get$e()?.run( 'ai-integration/open-text-to-elementor' );
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
content: [ { type: 'text', text: 'Text to Elementor interface opened.' } ],
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { z } from '@elementor/schema';
|
|
2
|
+
import { type McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
|
+
|
|
4
|
+
import type { ElementorContainer, McpToolResult, ToolParams } from '../types';
|
|
5
|
+
import { get$e, getElementor, getElementorCommon } from '../utils';
|
|
6
|
+
import { validateDynamicTagDisabled, validateDynamicTagEnabled } from '../validation-helpers';
|
|
7
|
+
|
|
8
|
+
export function addDynamicTool( server: McpServer ): void {
|
|
9
|
+
server.registerTool(
|
|
10
|
+
'dynamic',
|
|
11
|
+
{
|
|
12
|
+
description:
|
|
13
|
+
'Manage dynamic-tags content for Elementor elements including getting dynamic settings, enabling and disabling dynamic tags.',
|
|
14
|
+
inputSchema: {
|
|
15
|
+
action: z
|
|
16
|
+
.enum( [ 'get-settings', 'enable', 'disable' ] )
|
|
17
|
+
.describe( 'The dynamic content operation to perform' ),
|
|
18
|
+
elementId: z.string().describe( 'The ID of the element to modify' ),
|
|
19
|
+
controlName: z.string().describe( 'The name of the control/setting to make dynamic' ),
|
|
20
|
+
dynamicName: z
|
|
21
|
+
.string()
|
|
22
|
+
.optional()
|
|
23
|
+
.describe(
|
|
24
|
+
'The name of the dynamic tag to enable. Required for enable action. Output of get-settings action.'
|
|
25
|
+
),
|
|
26
|
+
settings: z
|
|
27
|
+
.object( {} )
|
|
28
|
+
.catchall( z.unknown() )
|
|
29
|
+
.optional()
|
|
30
|
+
.describe(
|
|
31
|
+
'The settings to apply to the dynamic tag. Used with enable action. Output of get-settings action.'
|
|
32
|
+
),
|
|
33
|
+
hasRunGetDynamicSettings: z
|
|
34
|
+
.boolean()
|
|
35
|
+
.optional()
|
|
36
|
+
.describe(
|
|
37
|
+
'Whether the get-settings action has already been run. Must be set to true when using enable action.'
|
|
38
|
+
),
|
|
39
|
+
},
|
|
40
|
+
annotations: {
|
|
41
|
+
title: 'Manage Dynamic Content',
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
async ( params: ToolParams ) => {
|
|
45
|
+
switch ( params.action ) {
|
|
46
|
+
case 'get-settings':
|
|
47
|
+
return await handleGetDynamicSettings( params );
|
|
48
|
+
case 'enable':
|
|
49
|
+
if ( params.hasRunGetDynamicSettings !== true ) {
|
|
50
|
+
throw new Error(
|
|
51
|
+
'get-dynamic-settings action has not been run. Run it first before using the enable action.'
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
if ( ! params.elementId || ! params.controlName || ! params.dynamicName || ! params.settings ) {
|
|
55
|
+
throw new Error(
|
|
56
|
+
'elementId, controlName, dynamicName, and settings are required for dynamic enable'
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
return await handleDynamicEnable( params );
|
|
60
|
+
case 'disable':
|
|
61
|
+
if ( ! params.elementId || ! params.controlName ) {
|
|
62
|
+
throw new Error( 'elementId and controlName are required for dynamic disable' );
|
|
63
|
+
}
|
|
64
|
+
return await handleDynamicDisable( params );
|
|
65
|
+
default:
|
|
66
|
+
throw new Error( `Unknown action: ${ params.action }` );
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function handleGetDynamicSettings( params: ToolParams ): Promise< McpToolResult > {
|
|
73
|
+
if ( ! params.elementId || ! params.controlName ) {
|
|
74
|
+
throw new Error( 'elementId and controlName are required for get-settings' );
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const elementor = getElementor();
|
|
78
|
+
const container = elementor?.getContainer( params.elementId as string );
|
|
79
|
+
if ( ! container ) {
|
|
80
|
+
throw new Error( `Element with ID ${ params.elementId } not found.` );
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const controls = container.settings.controls as Record< string, { dynamic?: { categories?: string[] } } >;
|
|
84
|
+
const control = controls[ params.controlName as string ];
|
|
85
|
+
|
|
86
|
+
if ( ! control ) {
|
|
87
|
+
throw new Error( `Control "${ params.controlName }" not found on element ${ params.elementId }.` );
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if ( ! control.dynamic?.categories ) {
|
|
91
|
+
throw new Error( `Control "${ params.controlName }" does not support dynamic content.` );
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const { categories } = control.dynamic;
|
|
95
|
+
const dynamicTags = elementor?.dynamicTags;
|
|
96
|
+
|
|
97
|
+
if ( ! dynamicTags?.getConfig ) {
|
|
98
|
+
throw new Error( 'Dynamic tags API is not available.' );
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const tags = dynamicTags.getConfig( 'tags' ) as Record< string, { categories: string[] } >;
|
|
102
|
+
const relevantTags = Object.values( tags ).filter( ( tag ) =>
|
|
103
|
+
tag.categories.find( ( category ) => categories.includes( category ) )
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
content: [ { type: 'text', text: JSON.stringify( relevantTags, null, 2 ) } ],
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function handleDynamicEnable( params: ToolParams ): Promise< McpToolResult > {
|
|
112
|
+
if ( ! params.elementId || ! params.controlName || ! params.dynamicName ) {
|
|
113
|
+
throw new Error( 'elementId, controlName, and dynamicName are required for dynamic enable' );
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if ( params.hasRunGetDynamicSettings !== true ) {
|
|
117
|
+
throw new Error( 'get-dynamic-settings action has not been run. Run it first before using the enable action.' );
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const elementor = getElementor();
|
|
121
|
+
const container = elementor?.getContainer( params.elementId as string );
|
|
122
|
+
if ( ! container ) {
|
|
123
|
+
throw new Error( `Element with ID ${ params.elementId } not found.` );
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const dynamicName = ( params.dynamicName as string )
|
|
127
|
+
.toLowerCase()
|
|
128
|
+
.replace( /\s+/g, '-' )
|
|
129
|
+
.replace( /_/g, '-' )
|
|
130
|
+
.replace( /[^a-z0-9-]/g, '' );
|
|
131
|
+
|
|
132
|
+
const settings = ( params.settings || {} ) as Record< string, unknown > & {
|
|
133
|
+
toJSON?: () => Record< string, unknown >;
|
|
134
|
+
};
|
|
135
|
+
settings.toJSON = () => settings;
|
|
136
|
+
|
|
137
|
+
const elementorCommon = getElementorCommon();
|
|
138
|
+
if ( ! elementorCommon?.helpers?.getUniqueId ) {
|
|
139
|
+
throw new Error( 'Elementor Common API is not available.' );
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const uniqueId = elementorCommon.helpers.getUniqueId();
|
|
143
|
+
|
|
144
|
+
const dynamicTags = elementor?.dynamicTags;
|
|
145
|
+
|
|
146
|
+
if ( ! dynamicTags?.tagDataToTagText ) {
|
|
147
|
+
throw new Error( 'Dynamic tags API is not available.' );
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const tagText = dynamicTags.tagDataToTagText( String( uniqueId ), dynamicName, settings );
|
|
151
|
+
|
|
152
|
+
await get$e()?.run( 'document/dynamic/enable', {
|
|
153
|
+
container,
|
|
154
|
+
settings: { [ params.controlName as string ]: tagText },
|
|
155
|
+
} );
|
|
156
|
+
|
|
157
|
+
validateDynamicTagEnabled( container, params.controlName as string );
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
content: [
|
|
161
|
+
{
|
|
162
|
+
type: 'text',
|
|
163
|
+
text: `Dynamic content enabled for element ${ params.elementId }, control "${ params.controlName }" with dynamic tag "${ dynamicName }": ${ tagText }`,
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async function handleDynamicDisable( params: ToolParams ): Promise< McpToolResult > {
|
|
170
|
+
if ( ! params.elementId || ! params.controlName ) {
|
|
171
|
+
throw new Error( 'elementId and controlName are required for dynamic disable' );
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const container = getElementor()?.getContainer( params.elementId as string );
|
|
175
|
+
if ( ! container ) {
|
|
176
|
+
throw new Error( `Element with ID ${ params.elementId } not found.` );
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const getElementDynamicSetting = ( elementContainer: ElementorContainer ) => {
|
|
180
|
+
const modelSettings = ( elementContainer.model?.attributes?.settings || {} ) as Record< string, unknown >;
|
|
181
|
+
|
|
182
|
+
const dynamicContent: Record< string, unknown > = {};
|
|
183
|
+
|
|
184
|
+
Object.keys( modelSettings ).forEach( ( key ) => {
|
|
185
|
+
const value = modelSettings[ key ];
|
|
186
|
+
let dynamicData = null;
|
|
187
|
+
|
|
188
|
+
if ( value && typeof value === 'object' && ( value as { __dynamic__?: unknown } ).__dynamic__ ) {
|
|
189
|
+
dynamicData = ( value as { __dynamic__: unknown } ).__dynamic__;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if ( dynamicData ) {
|
|
193
|
+
dynamicContent[ key ] = dynamicData;
|
|
194
|
+
}
|
|
195
|
+
} );
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
settingsNames: Object.keys(
|
|
199
|
+
( dynamicContent as { attributes?: Record< string, unknown > } ).attributes || {}
|
|
200
|
+
),
|
|
201
|
+
dynamicContent,
|
|
202
|
+
};
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const availableDynamicSettingNames = getElementDynamicSetting( container ).settingsNames;
|
|
206
|
+
|
|
207
|
+
if ( ! availableDynamicSettingNames.includes( params.controlName as string ) ) {
|
|
208
|
+
throw new Error(
|
|
209
|
+
`Setting "${ params.controlName }" on element ${
|
|
210
|
+
params.elementId
|
|
211
|
+
} does not have dynamic content enabled. here is the list of dynamic settings available: ${ JSON.stringify(
|
|
212
|
+
availableDynamicSettingNames,
|
|
213
|
+
null,
|
|
214
|
+
2
|
|
215
|
+
) }`
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
await get$e()?.run( 'document/dynamic/disable', {
|
|
220
|
+
container,
|
|
221
|
+
settings: {
|
|
222
|
+
[ params.controlName as string ]: '',
|
|
223
|
+
},
|
|
224
|
+
} );
|
|
225
|
+
|
|
226
|
+
validateDynamicTagDisabled( container, params.controlName as string );
|
|
227
|
+
|
|
228
|
+
return {
|
|
229
|
+
content: [
|
|
230
|
+
{
|
|
231
|
+
type: 'text',
|
|
232
|
+
text: `Dynamic content disabled for setting "${ params.controlName }" on element ${ params.elementId }.`,
|
|
233
|
+
},
|
|
234
|
+
],
|
|
235
|
+
};
|
|
236
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { addAiTool } from './ai-tool';
|
|
2
|
+
export { addDynamicTool } from './dynamic-tool';
|
|
3
|
+
export { addPageTool } from './page-tool';
|
|
4
|
+
export { addRoutesTool } from './routes-tool';
|
|
5
|
+
export { addStylingTool } from './styling-tool';
|
|
6
|
+
export { addUiTool } from './ui-tool';
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { z } from '@elementor/schema';
|
|
2
|
+
import { type McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
|
+
|
|
4
|
+
import { RESOURCE_URI_PAGE_SETTINGS } from '../resources';
|
|
5
|
+
import type { ElementorContainer, McpToolResult, ToolParams } from '../types';
|
|
6
|
+
import { encodeToolJson, get$e, getElementor } from '../utils';
|
|
7
|
+
import { validateDocumentSettingsUpdated } from '../validation-helpers';
|
|
8
|
+
|
|
9
|
+
export function addPageTool( server: McpServer ): void {
|
|
10
|
+
server.registerTool(
|
|
11
|
+
'page',
|
|
12
|
+
{
|
|
13
|
+
description: `Manage page and document operations including undo/redo, saving, page settings, and page information. Use this tool when you need to:
|
|
14
|
+
- Undo or redo changes (history-undo, history-redo, history-undo-all) - Use these when user asks to undo, revert, or redo recent changes
|
|
15
|
+
- Save page changes (save-draft, save-publish, save-update, save-discard)
|
|
16
|
+
- Get or update page settings like page title, description, keywords, styling (get-settings, update-settings)
|
|
17
|
+
- Open or preview pages (open, preview)
|
|
18
|
+
This tool handles document-level operations and change history.`,
|
|
19
|
+
inputSchema: {
|
|
20
|
+
action: z
|
|
21
|
+
.enum( [
|
|
22
|
+
'save-draft',
|
|
23
|
+
'save-publish',
|
|
24
|
+
'save-update',
|
|
25
|
+
'save-discard',
|
|
26
|
+
'history-undo',
|
|
27
|
+
'history-redo',
|
|
28
|
+
'history-undo-all',
|
|
29
|
+
'get-settings',
|
|
30
|
+
'update-settings',
|
|
31
|
+
'open',
|
|
32
|
+
'preview',
|
|
33
|
+
] )
|
|
34
|
+
.describe(
|
|
35
|
+
'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)'
|
|
36
|
+
),
|
|
37
|
+
pageId: z.string().optional().describe( 'Page/document ID for open action' ),
|
|
38
|
+
settings: z
|
|
39
|
+
.record( z.unknown() )
|
|
40
|
+
.optional()
|
|
41
|
+
.describe(
|
|
42
|
+
'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"}.'
|
|
43
|
+
),
|
|
44
|
+
},
|
|
45
|
+
annotations: {
|
|
46
|
+
title: 'Manage Page',
|
|
47
|
+
},
|
|
48
|
+
_meta: {
|
|
49
|
+
'angie:required-resources': [
|
|
50
|
+
{
|
|
51
|
+
uri: RESOURCE_URI_PAGE_SETTINGS,
|
|
52
|
+
whenToUse:
|
|
53
|
+
'When updating page settings (action=update-settings) to understand the page schema, available settings, their allowed values, and current page configuration',
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
async ( params: ToolParams ) => {
|
|
59
|
+
switch ( params.action ) {
|
|
60
|
+
case 'save-draft':
|
|
61
|
+
return await handleSavePageDraft();
|
|
62
|
+
case 'save-publish':
|
|
63
|
+
return await handleSavePagePublish();
|
|
64
|
+
case 'save-update':
|
|
65
|
+
return await handleSavePageUpdate();
|
|
66
|
+
case 'save-discard':
|
|
67
|
+
return await handleSavePageDiscard();
|
|
68
|
+
case 'history-undo':
|
|
69
|
+
return await handleHistoryUndo();
|
|
70
|
+
case 'history-redo':
|
|
71
|
+
return await handleHistoryRedo();
|
|
72
|
+
case 'history-undo-all':
|
|
73
|
+
return await handleHistoryUndoAll();
|
|
74
|
+
case 'get-settings':
|
|
75
|
+
return await handleGetDocumentSettings();
|
|
76
|
+
case 'update-settings':
|
|
77
|
+
return await handleUpdateDocumentSettings( params );
|
|
78
|
+
case 'open':
|
|
79
|
+
return await handleOpenPage( params );
|
|
80
|
+
case 'preview':
|
|
81
|
+
return await handlePreviewPage();
|
|
82
|
+
default:
|
|
83
|
+
throw new Error( `Unknown action: ${ params.action }` );
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function handleSavePageDraft(): Promise< McpToolResult > {
|
|
90
|
+
await get$e()?.run( 'document/save/draft' );
|
|
91
|
+
return {
|
|
92
|
+
content: [ { type: 'text', text: 'Page saved as draft.' } ],
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function handleSavePagePublish(): Promise< McpToolResult > {
|
|
97
|
+
await get$e()?.run( 'document/save/publish' );
|
|
98
|
+
return {
|
|
99
|
+
content: [ { type: 'text', text: 'Page published.' } ],
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function handleSavePageUpdate(): Promise< McpToolResult > {
|
|
104
|
+
await get$e()?.run( 'document/save/update' );
|
|
105
|
+
return {
|
|
106
|
+
content: [ { type: 'text', text: 'Page updated.' } ],
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function handleSavePageDiscard(): Promise< McpToolResult > {
|
|
111
|
+
await get$e()?.run( 'document/save/discard' );
|
|
112
|
+
return {
|
|
113
|
+
content: [ { type: 'text', text: 'Page changes discarded.' } ],
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function handleHistoryUndo(): Promise< McpToolResult > {
|
|
118
|
+
await get$e()?.run( 'document/history/undo' );
|
|
119
|
+
return {
|
|
120
|
+
content: [ { type: 'text', text: 'Undo performed.' } ],
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function handleHistoryRedo(): Promise< McpToolResult > {
|
|
125
|
+
await get$e()?.run( 'document/history/redo' );
|
|
126
|
+
return {
|
|
127
|
+
content: [ { type: 'text', text: 'Redo performed.' } ],
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function handleHistoryUndoAll(): Promise< McpToolResult > {
|
|
132
|
+
await get$e()?.run( 'document/history/undo-all', { document: getElementor()?.documents.getCurrent() } );
|
|
133
|
+
return {
|
|
134
|
+
content: [ { type: 'text', text: 'All changes undone.' } ],
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async function handleOpenPage( params: ToolParams ): Promise< McpToolResult > {
|
|
139
|
+
if ( ! params.pageId ) {
|
|
140
|
+
throw new Error( 'pageId is required for open action' );
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
await get$e()?.run( 'editor/documents/open', {
|
|
144
|
+
id: params.pageId,
|
|
145
|
+
} );
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
content: [ { type: 'text', text: `Page ${ params.pageId } opened.` } ],
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async function handlePreviewPage(): Promise< McpToolResult > {
|
|
153
|
+
await get$e()?.run( 'editor/documents/preview' );
|
|
154
|
+
return {
|
|
155
|
+
content: [ { type: 'text', text: 'Page preview opened.' } ],
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async function handleGetDocumentSettings(): Promise< McpToolResult > {
|
|
160
|
+
const document = getElementor()?.documents?.getCurrent();
|
|
161
|
+
if ( ! document ) {
|
|
162
|
+
throw new Error( 'No active document found.' );
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const settings = document.config?.settings || {};
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
content: [
|
|
169
|
+
{
|
|
170
|
+
type: 'text',
|
|
171
|
+
text: encodeToolJson( {
|
|
172
|
+
documentId: document.id,
|
|
173
|
+
documentType: document.config?.type,
|
|
174
|
+
settings,
|
|
175
|
+
} ),
|
|
176
|
+
},
|
|
177
|
+
],
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async function handleUpdateDocumentSettings( params: ToolParams ): Promise< McpToolResult > {
|
|
182
|
+
const currentDocument = getElementor()?.documents?.getCurrent();
|
|
183
|
+
if ( ! currentDocument ) {
|
|
184
|
+
throw new Error( 'No active document found.' );
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if ( ! params.settings || typeof params.settings !== 'object' ) {
|
|
188
|
+
throw new Error( 'settings object is required for update-settings action' );
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
await get$e()?.run( 'document/elements/settings', {
|
|
192
|
+
container: currentDocument.container,
|
|
193
|
+
settings: params.settings,
|
|
194
|
+
options: {
|
|
195
|
+
external: true,
|
|
196
|
+
},
|
|
197
|
+
} );
|
|
198
|
+
|
|
199
|
+
const updatedDocument = getElementor()?.documents?.getCurrent();
|
|
200
|
+
if ( ! updatedDocument?.container ) {
|
|
201
|
+
throw new Error( 'Document container not found after update' );
|
|
202
|
+
}
|
|
203
|
+
validateDocumentSettingsUpdated( updatedDocument.container as unknown as ElementorContainer );
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
content: [
|
|
207
|
+
{
|
|
208
|
+
type: 'text',
|
|
209
|
+
text: JSON.stringify( {
|
|
210
|
+
success: true,
|
|
211
|
+
message: `Document settings updated successfully. Settings: ${ JSON.stringify(
|
|
212
|
+
params.settings,
|
|
213
|
+
null,
|
|
214
|
+
2
|
|
215
|
+
) }`,
|
|
216
|
+
saveChangesSuggestion: 'Suggest the following quick user replies: "Publish Changes", "Save Draft"',
|
|
217
|
+
nextStep: 'Page settings updated in editor. User should save the page to persist changes.',
|
|
218
|
+
} ),
|
|
219
|
+
},
|
|
220
|
+
],
|
|
221
|
+
};
|
|
222
|
+
}
|