@elementor/editor-canvas 4.3.0-991 → 4.3.0-992
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +251 -409
- package/dist/index.mjs +122 -282
- package/package.json +20 -20
- package/src/mcp/resources/__tests__/available-widgets-resource.test.ts +76 -0
- package/src/mcp/resources/__tests__/widgets-schema-resource.test.ts +79 -76
- package/src/mcp/resources/available-widgets-resource.ts +31 -40
- package/src/mcp/resources/widgets-schema-resource.ts +35 -150
- package/src/mcp/resources/build-llm-guidance.ts +0 -110
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elementor/editor-canvas",
|
|
3
3
|
"description": "Elementor Editor Canvas",
|
|
4
|
-
"version": "4.3.0-
|
|
4
|
+
"version": "4.3.0-992",
|
|
5
5
|
"private": false,
|
|
6
6
|
"author": "Elementor Team",
|
|
7
7
|
"homepage": "https://elementor.com/",
|
|
@@ -37,26 +37,26 @@
|
|
|
37
37
|
"react-dom": "^18.3.1"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@elementor/editor": "4.3.0-
|
|
41
|
-
"@elementor/editor-controls": "4.3.0-
|
|
42
|
-
"@elementor/editor-documents": "4.3.0-
|
|
43
|
-
"@elementor/editor-elements": "4.3.0-
|
|
44
|
-
"@elementor/editor-interactions": "4.3.0-
|
|
45
|
-
"@elementor/editor-mcp": "4.3.0-
|
|
46
|
-
"@elementor/editor-notifications": "4.3.0-
|
|
47
|
-
"@elementor/editor-props": "4.3.0-
|
|
48
|
-
"@elementor/editor-responsive": "4.3.0-
|
|
49
|
-
"@elementor/editor-styles": "4.3.0-
|
|
50
|
-
"@elementor/editor-styles-repository": "4.3.0-
|
|
51
|
-
"@elementor/editor-ui": "4.3.0-
|
|
52
|
-
"@elementor/editor-v1-adapters": "4.3.0-
|
|
53
|
-
"@elementor/events": "4.3.0-
|
|
54
|
-
"@elementor/http-client": "4.3.0-
|
|
55
|
-
"@elementor/schema": "4.3.0-
|
|
56
|
-
"@elementor/twing": "4.3.0-
|
|
40
|
+
"@elementor/editor": "4.3.0-992",
|
|
41
|
+
"@elementor/editor-controls": "4.3.0-992",
|
|
42
|
+
"@elementor/editor-documents": "4.3.0-992",
|
|
43
|
+
"@elementor/editor-elements": "4.3.0-992",
|
|
44
|
+
"@elementor/editor-interactions": "4.3.0-992",
|
|
45
|
+
"@elementor/editor-mcp": "4.3.0-992",
|
|
46
|
+
"@elementor/editor-notifications": "4.3.0-992",
|
|
47
|
+
"@elementor/editor-props": "4.3.0-992",
|
|
48
|
+
"@elementor/editor-responsive": "4.3.0-992",
|
|
49
|
+
"@elementor/editor-styles": "4.3.0-992",
|
|
50
|
+
"@elementor/editor-styles-repository": "4.3.0-992",
|
|
51
|
+
"@elementor/editor-ui": "4.3.0-992",
|
|
52
|
+
"@elementor/editor-v1-adapters": "4.3.0-992",
|
|
53
|
+
"@elementor/events": "4.3.0-992",
|
|
54
|
+
"@elementor/http-client": "4.3.0-992",
|
|
55
|
+
"@elementor/schema": "4.3.0-992",
|
|
56
|
+
"@elementor/twing": "4.3.0-992",
|
|
57
57
|
"@elementor/ui": "1.37.5",
|
|
58
|
-
"@elementor/utils": "4.3.0-
|
|
59
|
-
"@elementor/wp-media": "4.3.0-
|
|
58
|
+
"@elementor/utils": "4.3.0-992",
|
|
59
|
+
"@elementor/wp-media": "4.3.0-992",
|
|
60
60
|
"@floating-ui/react": "^0.27.5",
|
|
61
61
|
"@wordpress/i18n": "^5.13.0",
|
|
62
62
|
"dompurify": "^3.2.6"
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { httpService } from '@elementor/http-client';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
AVAILABLE_WIDGETS_URI,
|
|
5
|
+
AVAILABLE_WIDGETS_URI_V4,
|
|
6
|
+
initAvailableWidgetsResource,
|
|
7
|
+
} from '../available-widgets-resource';
|
|
8
|
+
|
|
9
|
+
jest.mock( '@elementor/http-client', () => ( {
|
|
10
|
+
httpService: jest.fn(),
|
|
11
|
+
} ) );
|
|
12
|
+
|
|
13
|
+
const mockedHttpService = httpService as jest.MockedFunction< typeof httpService >;
|
|
14
|
+
|
|
15
|
+
type ResourceHandler = () => Promise< { contents: { text: string }[] } >;
|
|
16
|
+
|
|
17
|
+
const captureHandlers = () => {
|
|
18
|
+
const resource = jest.fn();
|
|
19
|
+
initAvailableWidgetsResource( { resource } as never );
|
|
20
|
+
return {
|
|
21
|
+
v4Handler: resource.mock.calls[ 0 ][ 3 ] as ResourceHandler,
|
|
22
|
+
allHandler: resource.mock.calls[ 1 ][ 3 ] as ResourceHandler,
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
describe( 'available-widgets-resource', () => {
|
|
27
|
+
beforeEach( () => {
|
|
28
|
+
jest.clearAllMocks();
|
|
29
|
+
} );
|
|
30
|
+
|
|
31
|
+
it( 'fetches all widgets via list-widgets without a version filter', async () => {
|
|
32
|
+
// Arrange
|
|
33
|
+
const post = jest.fn().mockResolvedValue( {
|
|
34
|
+
data: {
|
|
35
|
+
data: [
|
|
36
|
+
{ type: 'e-heading', version: 'v4' },
|
|
37
|
+
{ type: 'legacy-icon', version: 'v3' },
|
|
38
|
+
],
|
|
39
|
+
},
|
|
40
|
+
} );
|
|
41
|
+
mockedHttpService.mockReturnValue( { post } as never );
|
|
42
|
+
const { allHandler } = captureHandlers();
|
|
43
|
+
|
|
44
|
+
// Act
|
|
45
|
+
const result = await allHandler();
|
|
46
|
+
|
|
47
|
+
// Assert
|
|
48
|
+
expect( post ).toHaveBeenCalledWith( 'elementor/v1/mcp-proxy', { tool: 'list-widgets', input: {} } );
|
|
49
|
+
expect( JSON.parse( result.contents[ 0 ].text ) ).toEqual( [
|
|
50
|
+
{ type: 'e-heading', version: 'v4' },
|
|
51
|
+
{ type: 'legacy-icon', version: 'v3' },
|
|
52
|
+
] );
|
|
53
|
+
} );
|
|
54
|
+
|
|
55
|
+
it( 'fetches only v4 widgets via list-widgets with a version filter', async () => {
|
|
56
|
+
// Arrange
|
|
57
|
+
const post = jest.fn().mockResolvedValue( { data: { data: [ { type: 'e-heading', version: 'v4' } ] } } );
|
|
58
|
+
mockedHttpService.mockReturnValue( { post } as never );
|
|
59
|
+
const { v4Handler } = captureHandlers();
|
|
60
|
+
|
|
61
|
+
// Act
|
|
62
|
+
const result = await v4Handler();
|
|
63
|
+
|
|
64
|
+
// Assert
|
|
65
|
+
expect( post ).toHaveBeenCalledWith( 'elementor/v1/mcp-proxy', {
|
|
66
|
+
tool: 'list-widgets',
|
|
67
|
+
input: { version: 'v4' },
|
|
68
|
+
} );
|
|
69
|
+
expect( JSON.parse( result.contents[ 0 ].text ) ).toEqual( [ { type: 'e-heading', version: 'v4' } ] );
|
|
70
|
+
} );
|
|
71
|
+
|
|
72
|
+
it( 'exposes the expected resource URIs', () => {
|
|
73
|
+
expect( AVAILABLE_WIDGETS_URI ).toBe( 'elementor://context/available-widgets' );
|
|
74
|
+
expect( AVAILABLE_WIDGETS_URI_V4 ).toBe( 'elementor://context/available-widgets/v4' );
|
|
75
|
+
} );
|
|
76
|
+
} );
|
|
@@ -1,95 +1,98 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { httpService } from '@elementor/http-client';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
jest.mock( '@elementor/http-client', () => ( {
|
|
4
|
+
httpService: jest.fn(),
|
|
5
|
+
} ) );
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
message: { $$type: 'string', value: '[all-fields]' },
|
|
15
|
-
},
|
|
16
|
-
},
|
|
17
|
-
};
|
|
7
|
+
jest.mock( '@elementor/editor-mcp', () => {
|
|
8
|
+
class MockResourceTemplate {
|
|
9
|
+
callbacks: { list?: () => Promise< { resources: unknown[] } > };
|
|
10
|
+
constructor( _uriTemplate: string, callbacks: { list?: () => Promise< { resources: unknown[] } > } ) {
|
|
11
|
+
this.callbacks = callbacks;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return { ResourceTemplate: MockResourceTemplate };
|
|
15
|
+
} );
|
|
18
16
|
|
|
19
|
-
|
|
20
|
-
title: 'Form',
|
|
21
|
-
controls: {},
|
|
22
|
-
elType: 'widget',
|
|
23
|
-
meta: { is_container: true },
|
|
24
|
-
base_settings: mockEmailBaseSettings,
|
|
25
|
-
atomic_props_schema: {
|
|
26
|
-
email: { kind: 'object', key: 'emails' },
|
|
27
|
-
'form-name': { kind: 'string', key: 'string' },
|
|
28
|
-
},
|
|
29
|
-
} as unknown as V1ElementConfig;
|
|
17
|
+
import { initWidgetsSchemaResource, WIDGET_SCHEMA_URI } from '../widgets-schema-resource';
|
|
30
18
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
19
|
+
const mockedHttpService = httpService as jest.MockedFunction< typeof httpService >;
|
|
20
|
+
|
|
21
|
+
type ResourceTemplateHandler = (
|
|
22
|
+
uri: URL,
|
|
23
|
+
variables: Record< string, string >
|
|
24
|
+
) => Promise< { contents: { text: string }[] } >;
|
|
25
|
+
type ResourceTemplateLike = { callbacks: { list?: () => Promise< { resources: unknown[] } > } };
|
|
36
26
|
|
|
37
|
-
|
|
38
|
-
|
|
27
|
+
const captureHandlers = () => {
|
|
28
|
+
const resource = jest.fn();
|
|
29
|
+
initWidgetsSchemaResource( { resource } as never );
|
|
30
|
+
const call = resource.mock.calls[ 0 ];
|
|
31
|
+
const template = call[ 1 ] as ResourceTemplateLike;
|
|
32
|
+
return {
|
|
33
|
+
list: template.callbacks.list as () => Promise< { resources: unknown[] } >,
|
|
34
|
+
readHandler: call[ 3 ] as ResourceTemplateHandler,
|
|
35
|
+
};
|
|
36
|
+
};
|
|
39
37
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
38
|
+
describe( 'widgets-schema-resource', () => {
|
|
39
|
+
beforeEach( () => {
|
|
40
|
+
jest.clearAllMocks();
|
|
43
41
|
} );
|
|
44
42
|
|
|
45
|
-
it( '
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
display: { $$type: 'string', value: 'flex' },
|
|
54
|
-
},
|
|
55
|
-
},
|
|
56
|
-
],
|
|
57
|
-
},
|
|
43
|
+
it( 'lists widget types fetched from the server via the list-widgets tool', async () => {
|
|
44
|
+
// Arrange
|
|
45
|
+
const post = jest.fn().mockResolvedValue( {
|
|
46
|
+
data: {
|
|
47
|
+
data: [
|
|
48
|
+
{ type: 'e-heading', version: 'v4' },
|
|
49
|
+
{ type: 'e-button', version: 'v4' },
|
|
50
|
+
],
|
|
58
51
|
},
|
|
59
|
-
}
|
|
52
|
+
} );
|
|
53
|
+
mockedHttpService.mockReturnValue( { post } as never );
|
|
54
|
+
const { list } = captureHandlers();
|
|
60
55
|
|
|
61
|
-
|
|
56
|
+
// Act
|
|
57
|
+
const result = await list();
|
|
62
58
|
|
|
63
|
-
|
|
64
|
-
expect(
|
|
65
|
-
expect(
|
|
66
|
-
|
|
59
|
+
// Assert
|
|
60
|
+
expect( post ).toHaveBeenCalledWith( 'elementor/v1/mcp-proxy', { tool: 'list-widgets', input: {} } );
|
|
61
|
+
expect( result.resources ).toEqual( [
|
|
62
|
+
{ uri: 'elementor://widgets/schema/e-heading', name: 'Widget schema for e-heading' },
|
|
63
|
+
{ uri: 'elementor://widgets/schema/e-button', name: 'Widget schema for e-button' },
|
|
64
|
+
] );
|
|
67
65
|
} );
|
|
68
66
|
|
|
69
|
-
it( '
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
);
|
|
67
|
+
it( 'reads a single widget schema via the get-widget-schema tool', async () => {
|
|
68
|
+
// Arrange
|
|
69
|
+
const post = jest.fn().mockResolvedValue( {
|
|
70
|
+
data: { data: { type: 'object', properties: { text: { mocked: true } } } },
|
|
71
|
+
} );
|
|
72
|
+
mockedHttpService.mockReturnValue( { post } as never );
|
|
73
|
+
const { readHandler } = captureHandlers();
|
|
74
|
+
const uri = new URL( 'elementor://widgets/schema/e-heading' );
|
|
75
|
+
|
|
76
|
+
// Act
|
|
77
|
+
const result = await readHandler( uri, { widgetType: 'e-heading' } );
|
|
77
78
|
|
|
78
|
-
|
|
79
|
-
expect(
|
|
79
|
+
// Assert
|
|
80
|
+
expect( post ).toHaveBeenCalledWith( 'elementor/v1/mcp-proxy', {
|
|
81
|
+
tool: 'get-widget-schema',
|
|
82
|
+
input: { widget_type: 'e-heading' },
|
|
83
|
+
} );
|
|
84
|
+
expect( JSON.parse( result.contents[ 0 ].text ) ).toEqual( {
|
|
85
|
+
type: 'object',
|
|
86
|
+
properties: { text: { mocked: true } },
|
|
87
|
+
} );
|
|
80
88
|
} );
|
|
81
|
-
} );
|
|
82
89
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
email: { type: 'object', properties: {} },
|
|
88
|
-
},
|
|
89
|
-
Object.keys( mockEmailBaseSettings )
|
|
90
|
-
);
|
|
90
|
+
it( 'throws when no widget type variable is provided', async () => {
|
|
91
|
+
// Arrange
|
|
92
|
+
const { readHandler } = captureHandlers();
|
|
93
|
+
const uri = new URL( WIDGET_SCHEMA_URI.replace( '{widgetType}', '' ) );
|
|
91
94
|
|
|
92
|
-
|
|
93
|
-
expect(
|
|
95
|
+
// Act & Assert
|
|
96
|
+
await expect( readHandler( uri, {} ) ).rejects.toThrow( 'No widget type provided.' );
|
|
94
97
|
} );
|
|
95
98
|
} );
|
|
@@ -1,37 +1,42 @@
|
|
|
1
1
|
import { type MCPRegistryEntry } from '@elementor/editor-mcp';
|
|
2
|
-
import {
|
|
2
|
+
import { type HttpResponse, httpService } from '@elementor/http-client';
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
const MCP_PROXY_URL = 'elementor/v1/mcp-proxy';
|
|
5
5
|
|
|
6
6
|
export const AVAILABLE_WIDGETS_URI = 'elementor://context/available-widgets';
|
|
7
7
|
export const AVAILABLE_WIDGETS_URI_V4 = 'elementor://context/available-widgets/v4';
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
type WidgetSummary = {
|
|
10
|
+
type: string;
|
|
11
|
+
version: 'v3' | 'v4';
|
|
12
|
+
description?: string;
|
|
13
|
+
};
|
|
11
14
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
uri,
|
|
18
|
-
mimeType: 'application/json',
|
|
19
|
-
text: JSON.stringify( widgets, null, 2 ),
|
|
20
|
-
},
|
|
21
|
-
],
|
|
22
|
-
};
|
|
23
|
-
};
|
|
15
|
+
const fetchWidgets = async ( version?: WidgetSummary[ 'version' ] ): Promise< WidgetSummary[] > => {
|
|
16
|
+
const { data } = await httpService().post< HttpResponse< WidgetSummary[] > >( MCP_PROXY_URL, {
|
|
17
|
+
tool: 'list-widgets',
|
|
18
|
+
input: version ? { version } : {},
|
|
19
|
+
} );
|
|
24
20
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
21
|
+
return data.data ?? [];
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const buildContents = async ( uri: string, version?: WidgetSummary[ 'version' ] ) => {
|
|
25
|
+
const widgets = await fetchWidgets( version );
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
contents: [
|
|
29
|
+
{
|
|
30
|
+
uri,
|
|
31
|
+
mimeType: 'application/json',
|
|
32
|
+
text: JSON.stringify( widgets, null, 2 ),
|
|
33
|
+
},
|
|
34
|
+
],
|
|
34
35
|
};
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const initAvailableWidgetsResource = ( reg: MCPRegistryEntry ) => {
|
|
39
|
+
const { resource } = reg;
|
|
35
40
|
|
|
36
41
|
resource(
|
|
37
42
|
'available-widgets-v4',
|
|
@@ -39,7 +44,7 @@ export const initAvailableWidgetsResource = ( reg: MCPRegistryEntry ) => {
|
|
|
39
44
|
{
|
|
40
45
|
description: 'All registered v4 version widgets',
|
|
41
46
|
},
|
|
42
|
-
async () => buildContents( AVAILABLE_WIDGETS_URI_V4,
|
|
47
|
+
async () => buildContents( AVAILABLE_WIDGETS_URI_V4, 'v4' )
|
|
43
48
|
);
|
|
44
49
|
|
|
45
50
|
resource(
|
|
@@ -50,18 +55,4 @@ export const initAvailableWidgetsResource = ( reg: MCPRegistryEntry ) => {
|
|
|
50
55
|
},
|
|
51
56
|
async () => buildContents( AVAILABLE_WIDGETS_URI )
|
|
52
57
|
);
|
|
53
|
-
|
|
54
|
-
const eventName = v1ReadyEvent().name;
|
|
55
|
-
|
|
56
|
-
const onV1Ready = () => {
|
|
57
|
-
const widgets = getAvailableWidgets();
|
|
58
|
-
if ( widgets.length === 0 ) {
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
window.removeEventListener( eventName, onV1Ready );
|
|
62
|
-
notifyResourcesUpdated();
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
window.addEventListener( eventName, onV1Ready );
|
|
66
|
-
onV1Ready();
|
|
67
58
|
};
|
|
@@ -1,58 +1,5 @@
|
|
|
1
|
-
import { getWidgetsCache } from '@elementor/editor-elements';
|
|
2
1
|
import { type MCPRegistryEntry, ResourceTemplate } from '@elementor/editor-mcp';
|
|
3
|
-
import {
|
|
4
|
-
type ArrayPropType,
|
|
5
|
-
type ObjectPropType,
|
|
6
|
-
type PropType,
|
|
7
|
-
Schema,
|
|
8
|
-
type TransformablePropType,
|
|
9
|
-
type UnionPropType,
|
|
10
|
-
} from '@elementor/editor-props';
|
|
11
|
-
|
|
12
|
-
import { hasV3Controls, isWidgetAvailableForLLM } from '../utils/element-data-util';
|
|
13
|
-
import { buildLlmGuidance, enrichPropertiesWithBaseSettingsHints } from './build-llm-guidance';
|
|
14
|
-
|
|
15
|
-
const V3_LAYOUT_CONTROL_TYPES = new Set( [ 'section', 'tab', 'tabs' ] );
|
|
16
|
-
|
|
17
|
-
type V3ControlMetadataEntry = {
|
|
18
|
-
default?: unknown;
|
|
19
|
-
type?: string;
|
|
20
|
-
options?: unknown;
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
function extractV3ControlsMetadata( controls: unknown ): Record< string, V3ControlMetadataEntry > {
|
|
24
|
-
if ( ! hasV3Controls( controls ) ) {
|
|
25
|
-
return {};
|
|
26
|
-
}
|
|
27
|
-
const result: Record< string, V3ControlMetadataEntry > = {};
|
|
28
|
-
for ( const [ controlKey, raw ] of Object.entries( controls as Record< string, unknown > ) ) {
|
|
29
|
-
if ( ! raw || typeof raw !== 'object' ) {
|
|
30
|
-
continue;
|
|
31
|
-
}
|
|
32
|
-
const control = raw as Record< string, unknown >;
|
|
33
|
-
const controlType = typeof control.type === 'string' ? control.type : undefined;
|
|
34
|
-
if ( controlType && V3_LAYOUT_CONTROL_TYPES.has( controlType ) ) {
|
|
35
|
-
continue;
|
|
36
|
-
}
|
|
37
|
-
const entry: V3ControlMetadataEntry = {};
|
|
38
|
-
if ( Object.prototype.hasOwnProperty.call( control, 'default' ) ) {
|
|
39
|
-
entry.default = control.default;
|
|
40
|
-
}
|
|
41
|
-
if ( controlType ) {
|
|
42
|
-
entry.type = controlType;
|
|
43
|
-
}
|
|
44
|
-
if ( Object.prototype.hasOwnProperty.call( control, 'options' ) && control.options !== undefined ) {
|
|
45
|
-
const options = control.options;
|
|
46
|
-
if ( options && typeof options === 'object' && ! Array.isArray( options ) ) {
|
|
47
|
-
entry.options = Object.keys( options as Record< string, unknown > );
|
|
48
|
-
} else {
|
|
49
|
-
entry.options = options;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
result[ controlKey ] = entry;
|
|
53
|
-
}
|
|
54
|
-
return result;
|
|
55
|
-
}
|
|
2
|
+
import { type HttpResponse, httpService } from '@elementor/http-client';
|
|
56
3
|
|
|
57
4
|
export const CANVAS_SERVER_NAME = 'editor-canvas';
|
|
58
5
|
|
|
@@ -62,20 +9,43 @@ export const STYLE_SCHEMA_URI = 'elementor://styles/schema/{category}';
|
|
|
62
9
|
export const BEST_PRACTICES_URI = 'elementor://style/best-practices';
|
|
63
10
|
export const BEST_PRACTICES_FULL_URI = `${ CANVAS_SERVER_NAME }_${ BEST_PRACTICES_URI }`;
|
|
64
11
|
|
|
12
|
+
const MCP_PROXY_URL = 'elementor/v1/mcp-proxy';
|
|
13
|
+
|
|
14
|
+
type WidgetSummary = {
|
|
15
|
+
type: string;
|
|
16
|
+
version: 'v3' | 'v4';
|
|
17
|
+
description?: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const listWidgetTypes = async (): Promise< string[] > => {
|
|
21
|
+
const { data } = await httpService().post< HttpResponse< WidgetSummary[] > >( MCP_PROXY_URL, {
|
|
22
|
+
tool: 'list-widgets',
|
|
23
|
+
input: {},
|
|
24
|
+
} );
|
|
25
|
+
|
|
26
|
+
return ( data.data ?? [] ).map( ( widget ) => widget.type );
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const fetchWidgetSchema = async ( widgetType: string ): Promise< Record< string, unknown > > => {
|
|
30
|
+
const { data } = await httpService().post< HttpResponse< Record< string, unknown > > >( MCP_PROXY_URL, {
|
|
31
|
+
tool: 'get-widget-schema',
|
|
32
|
+
input: { widget_type: widgetType },
|
|
33
|
+
} );
|
|
34
|
+
|
|
35
|
+
return data.data ?? {};
|
|
36
|
+
};
|
|
37
|
+
|
|
65
38
|
export const initWidgetsSchemaResource = ( reg: MCPRegistryEntry ) => {
|
|
66
39
|
const { resource } = reg;
|
|
67
40
|
|
|
68
41
|
resource(
|
|
69
42
|
'widget-schema-by-type',
|
|
70
43
|
new ResourceTemplate( WIDGET_SCHEMA_URI, {
|
|
71
|
-
list: () => {
|
|
72
|
-
const
|
|
73
|
-
const availableWidgets = Object.keys( cache ).filter( ( widgetType ) =>
|
|
74
|
-
isWidgetAvailableForLLM( cache[ widgetType ] )
|
|
75
|
-
);
|
|
44
|
+
list: async () => {
|
|
45
|
+
const widgetTypes = await listWidgetTypes();
|
|
76
46
|
|
|
77
47
|
return {
|
|
78
|
-
resources:
|
|
48
|
+
resources: widgetTypes.map( ( widgetType ) => ( {
|
|
79
49
|
uri: `elementor://widgets/schema/${ widgetType }`,
|
|
80
50
|
name: 'Widget schema for ' + widgetType,
|
|
81
51
|
} ) ),
|
|
@@ -88,107 +58,22 @@ export const initWidgetsSchemaResource = ( reg: MCPRegistryEntry ) => {
|
|
|
88
58
|
async ( uri, variables ) => {
|
|
89
59
|
const widgetType =
|
|
90
60
|
typeof variables.widgetType === 'string' ? variables.widgetType : variables.widgetType?.[ 0 ];
|
|
91
|
-
const widgetData = getWidgetsCache()?.[ widgetType ];
|
|
92
|
-
if ( ! widgetData ) {
|
|
93
|
-
throw new Error( `No prop schema found for element type: ${ widgetType }` );
|
|
94
|
-
}
|
|
95
|
-
const propSchema = widgetData.atomic_props_schema;
|
|
96
|
-
if ( ! propSchema ) {
|
|
97
|
-
if ( ! hasV3Controls( widgetData.controls ) ) {
|
|
98
|
-
throw new Error( `No prop schema found for element type: ${ widgetType }` );
|
|
99
|
-
}
|
|
100
|
-
const controlMetadata = extractV3ControlsMetadata( widgetData.controls );
|
|
101
|
-
return {
|
|
102
|
-
contents: [
|
|
103
|
-
{
|
|
104
|
-
uri: uri.toString(),
|
|
105
|
-
mimeType: 'application/json',
|
|
106
|
-
text: JSON.stringify( {
|
|
107
|
-
widget_version: 'v3',
|
|
108
|
-
message:
|
|
109
|
-
'This widget exists in the editor but has no atomic props schema (V4). Use control_metadata as non-authoritative hints from legacy controls.',
|
|
110
|
-
fields_note: 'All settings are optional; there is no JSON schema for this widget type.',
|
|
111
|
-
properties: controlMetadata,
|
|
112
|
-
} ),
|
|
113
|
-
},
|
|
114
|
-
],
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
const baseSettingsKeys = Object.keys( widgetData?.base_settings ?? {} );
|
|
118
61
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
.filter( ( [ key, propType ] ) => Schema.isPropKeyConfigurable( key, propType as PropType ) )
|
|
123
|
-
.map( ( [ key, propType ] ) => [ key, Schema.propTypeToJsonSchema( propType ) ] )
|
|
124
|
-
),
|
|
125
|
-
baseSettingsKeys
|
|
126
|
-
);
|
|
127
|
-
|
|
128
|
-
const description =
|
|
129
|
-
typeof widgetData?.meta?.description === 'string' ? widgetData.meta.description : undefined;
|
|
62
|
+
if ( ! widgetType ) {
|
|
63
|
+
throw new Error( 'No widget type provided.' );
|
|
64
|
+
}
|
|
130
65
|
|
|
131
|
-
const
|
|
132
|
-
const llmGuidance = buildLlmGuidance( widgetData, widgetType, allWidgets );
|
|
66
|
+
const schema = await fetchWidgetSchema( widgetType );
|
|
133
67
|
|
|
134
68
|
return {
|
|
135
69
|
contents: [
|
|
136
70
|
{
|
|
137
71
|
uri: uri.toString(),
|
|
138
72
|
mimeType: 'application/json',
|
|
139
|
-
text: JSON.stringify(
|
|
140
|
-
type: 'object',
|
|
141
|
-
properties: asJson,
|
|
142
|
-
description,
|
|
143
|
-
llm_guidance: llmGuidance,
|
|
144
|
-
} ),
|
|
73
|
+
text: JSON.stringify( schema ),
|
|
145
74
|
},
|
|
146
75
|
],
|
|
147
76
|
};
|
|
148
77
|
}
|
|
149
78
|
);
|
|
150
79
|
};
|
|
151
|
-
|
|
152
|
-
function cleanupPropSchema( propSchema: Record< string, PropType > ): Record< string, PropType > {
|
|
153
|
-
const result: Record< string, Partial< PropType > > = {};
|
|
154
|
-
Object.keys( propSchema ).forEach( ( propName ) => {
|
|
155
|
-
result[ propName ] = cleanupPropType( propSchema[ propName ] );
|
|
156
|
-
} );
|
|
157
|
-
return result as Record< string, PropType >;
|
|
158
|
-
}
|
|
159
|
-
function cleanupPropType( propType: PropType & { key?: string } ): Partial< PropType > {
|
|
160
|
-
const result: Partial< PropType > = {};
|
|
161
|
-
Object.keys( propType ).forEach( ( property ) => {
|
|
162
|
-
switch ( property ) {
|
|
163
|
-
case 'key':
|
|
164
|
-
case 'kind':
|
|
165
|
-
( result as Record< string, unknown > )[ property ] = propType[ property ];
|
|
166
|
-
break;
|
|
167
|
-
case 'meta':
|
|
168
|
-
case 'settings':
|
|
169
|
-
{
|
|
170
|
-
if ( Object.keys( propType[ property ] || {} ).length > 0 ) {
|
|
171
|
-
( result as Record< string, unknown > )[ property ] = propType[ property ];
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
break;
|
|
175
|
-
}
|
|
176
|
-
} );
|
|
177
|
-
if ( result.kind === 'plain' ) {
|
|
178
|
-
Object.defineProperty( result, 'kind', { value: 'string' } );
|
|
179
|
-
} else if ( result.kind === 'array' ) {
|
|
180
|
-
result.item_prop_type = cleanupPropType( ( propType as ArrayPropType ).item_prop_type ) as PropType;
|
|
181
|
-
} else if ( result.kind === 'object' ) {
|
|
182
|
-
const shape = ( propType as ObjectPropType ).shape as Record< string, PropType >;
|
|
183
|
-
const cleanedShape = cleanupPropSchema( shape );
|
|
184
|
-
result.shape = cleanedShape;
|
|
185
|
-
} else if ( result.kind === 'union' ) {
|
|
186
|
-
const propTypes = ( propType as UnionPropType ).prop_types;
|
|
187
|
-
const cleanedPropTypes: Record< string, Partial< PropType > > = {};
|
|
188
|
-
Object.keys( propTypes ).forEach( ( key ) => {
|
|
189
|
-
cleanedPropTypes[ key ] = cleanupPropType( propTypes[ key ] );
|
|
190
|
-
} );
|
|
191
|
-
result.prop_types = cleanedPropTypes as Record< string, TransformablePropType >;
|
|
192
|
-
}
|
|
193
|
-
return result;
|
|
194
|
-
}
|