@elementor/editor-canvas 4.2.0-beta2 → 4.3.0-1001

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.
Files changed (33) hide show
  1. package/dist/index.d.mts +1 -1
  2. package/dist/index.d.ts +1 -1
  3. package/dist/index.js +449 -1319
  4. package/dist/index.mjs +314 -1197
  5. package/package.json +22 -22
  6. package/src/index.ts +1 -1
  7. package/src/legacy/__tests__/init-legacy-views.test.ts +1 -1
  8. package/src/mcp/canvas-mcp.ts +4 -2
  9. package/src/mcp/resources/__tests__/available-widgets-resource.test.ts +85 -0
  10. package/src/mcp/resources/__tests__/dynamic-tags-resource.test.ts +27 -37
  11. package/src/mcp/resources/__tests__/widgets-schema-resource.test.ts +82 -74
  12. package/src/mcp/resources/available-widgets-resource.ts +35 -41
  13. package/src/mcp/resources/best-practices-resource.ts +34 -0
  14. package/src/mcp/resources/dynamic-tags-resource.ts +28 -29
  15. package/src/mcp/resources/widgets-schema-resource.ts +37 -177
  16. package/src/mcp/tools/build-composition/tool.ts +107 -218
  17. package/src/sync/element-added-event.ts +8 -0
  18. package/src/utils/__tests__/grid-outline-utils.test.ts +39 -0
  19. package/src/utils/grid-outline-utils.ts +13 -2
  20. package/src/composition-builder/__tests__/composition-builder.test.ts +0 -352
  21. package/src/composition-builder/composition-builder.ts +0 -350
  22. package/src/composition-builder/utils/__tests__/required-children-enforcer.test.ts +0 -79
  23. package/src/composition-builder/utils/__tests__/required-default-child-tags.test.ts +0 -35
  24. package/src/composition-builder/utils/required-children-enforcer.ts +0 -56
  25. package/src/composition-builder/utils/required-default-child-tags.ts +0 -22
  26. package/src/mcp/resources/best-practices.ts +0 -159
  27. package/src/mcp/resources/build-llm-guidance.ts +0 -110
  28. package/src/mcp/tools/build-composition/__tests__/xml-leaf-wrapper.test.ts +0 -117
  29. package/src/mcp/tools/build-composition/prompt.ts +0 -167
  30. package/src/mcp/tools/build-composition/schema.ts +0 -42
  31. package/src/mcp/tools/build-composition/xml-leaf-wrapper.ts +0 -68
  32. package/src/mcp/utils/__tests__/get-composition-target-container.test.ts +0 -59
  33. package/src/mcp/utils/get-composition-target-container.ts +0 -15
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.2.0-beta2",
4
+ "version": "4.3.0-1001",
5
5
  "private": false,
6
6
  "author": "Elementor Team",
7
7
  "homepage": "https://elementor.com/",
@@ -37,29 +37,29 @@
37
37
  "react-dom": "^18.3.1"
38
38
  },
39
39
  "dependencies": {
40
- "@elementor/editor": "4.2.0-beta2",
41
- "dompurify": "^3.2.6",
42
- "@elementor/editor-controls": "4.2.0-beta2",
43
- "@elementor/editor-documents": "4.2.0-beta2",
44
- "@elementor/editor-elements": "4.2.0-beta2",
45
- "@elementor/editor-interactions": "4.2.0-beta2",
46
- "@elementor/editor-mcp": "4.2.0-beta2",
47
- "@elementor/editor-notifications": "4.2.0-beta2",
48
- "@elementor/editor-props": "4.2.0-beta2",
49
- "@elementor/editor-responsive": "4.2.0-beta2",
50
- "@elementor/editor-styles": "4.2.0-beta2",
51
- "@elementor/editor-styles-repository": "4.2.0-beta2",
52
- "@elementor/editor-ui": "4.2.0-beta2",
53
- "@elementor/editor-v1-adapters": "4.2.0-beta2",
54
- "@elementor/events": "4.2.0-beta2",
55
- "@elementor/http-client": "4.2.0-beta2",
56
- "@elementor/schema": "4.2.0-beta2",
57
- "@elementor/twing": "4.2.0-beta2",
40
+ "@elementor/editor": "4.3.0-1001",
41
+ "@elementor/editor-controls": "4.3.0-1001",
42
+ "@elementor/editor-documents": "4.3.0-1001",
43
+ "@elementor/editor-elements": "4.3.0-1001",
44
+ "@elementor/editor-interactions": "4.3.0-1001",
45
+ "@elementor/editor-mcp": "4.3.0-1001",
46
+ "@elementor/editor-notifications": "4.3.0-1001",
47
+ "@elementor/editor-props": "4.3.0-1001",
48
+ "@elementor/editor-responsive": "4.3.0-1001",
49
+ "@elementor/editor-styles": "4.3.0-1001",
50
+ "@elementor/editor-styles-repository": "4.3.0-1001",
51
+ "@elementor/editor-ui": "4.3.0-1001",
52
+ "@elementor/editor-v1-adapters": "4.3.0-1001",
53
+ "@elementor/events": "4.3.0-1001",
54
+ "@elementor/http-client": "4.3.0-1001",
55
+ "@elementor/schema": "4.3.0-1001",
56
+ "@elementor/twing": "4.3.0-1001",
58
57
  "@elementor/ui": "1.37.5",
59
- "@elementor/utils": "4.2.0-beta2",
60
- "@elementor/wp-media": "4.2.0-beta2",
58
+ "@elementor/utils": "4.3.0-1001",
59
+ "@elementor/wp-media": "4.3.0-1001",
61
60
  "@floating-ui/react": "^0.27.5",
62
- "@wordpress/i18n": "^5.13.0"
61
+ "@wordpress/i18n": "^5.13.0",
62
+ "dompurify": "^3.2.6"
63
63
  },
64
64
  "devDependencies": {
65
65
  "tsup": "^8.3.5"
package/src/index.ts CHANGED
@@ -44,4 +44,4 @@ export { UnknownStyleTypeError, UnknownStyleStateError } from './renderers/error
44
44
  export { useCanvasDocument } from './hooks/use-canvas-document';
45
45
  export { useEscapeOnCanvas } from './hooks/use-escape-on-canvas';
46
46
  export { doAfterRender } from './utils/after-render';
47
- export { ELEMENT_ADDED_EVENT, type ElementAddedEvent } from './mcp/tools/build-composition/tool';
47
+ export { ELEMENT_ADDED_EVENT, type ElementAddedEvent } from './sync/element-added-event';
@@ -45,7 +45,7 @@ describe( 'initLegacyViews', () => {
45
45
  mockLegacyElementor();
46
46
  } );
47
47
 
48
- it( 'should register a late element type via the legacy manager without leaking the state', () => {
48
+ it( 'should register a late element type via the legacy manager without leaking state', () => {
49
49
  // Arrange
50
50
  const elementsManager = createMockElementsManager();
51
51
  attachMockElementsManager( elementsManager );
@@ -2,6 +2,7 @@ import { type MCPRegistryEntry } from '@elementor/editor-mcp';
2
2
  import { Schema } from '@elementor/editor-props';
3
3
 
4
4
  import { initAvailableWidgetsResource } from './resources/available-widgets-resource';
5
+ import { initBestPracticesResource } from './resources/best-practices-resource';
5
6
  import { initBreakpointsResource } from './resources/breakpoints-resource';
6
7
  import { initDocumentStructureResource } from './resources/document-structure-resource';
7
8
  import { initDynamicTagsResource } from './resources/dynamic-tags-resource';
@@ -9,7 +10,7 @@ import { initEditorStateResource } from './resources/editor-state-resource';
9
10
  import { initGeneralContextResource } from './resources/general-context-resource';
10
11
  import { initSelectedElementResource } from './resources/selected-element-resource';
11
12
  import { initWidgetsSchemaResource } from './resources/widgets-schema-resource';
12
- import { initBuildCompositionsTool } from './tools/build-composition/tool';
13
+ import { initBuildCompositionTool } from './tools/build-composition/tool';
13
14
  import { initConfigureElementTool } from './tools/configure-element/tool';
14
15
  import { initGetElementConfigTool } from './tools/get-element-config/tool';
15
16
  import { getDynamicTagNamesByCategories } from './utils/resolve-dynamic-tag';
@@ -26,8 +27,9 @@ export const initCanvasMcp = ( reg: MCPRegistryEntry ) => {
26
27
  initSelectedElementResource( reg );
27
28
  initEditorStateResource( reg );
28
29
  initGeneralContextResource( reg );
29
- initBuildCompositionsTool( reg );
30
+ initBestPracticesResource( reg );
30
31
  initGetElementConfigTool( reg );
31
32
  initConfigureElementTool( reg );
33
+ initBuildCompositionTool( reg );
32
34
  initBreakpointsResource( reg );
33
35
  };
@@ -0,0 +1,85 @@
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-widget-schemas with summary=true', async () => {
32
+ // Arrange
33
+ const post = jest.fn().mockResolvedValue( {
34
+ data: {
35
+ data: {
36
+ widgets: [
37
+ { type: 'e-heading', description: 'A heading widget' },
38
+ { type: 'e-button', description: 'A button widget' },
39
+ ],
40
+ },
41
+ },
42
+ } );
43
+ mockedHttpService.mockReturnValue( { post } as never );
44
+ const { allHandler } = captureHandlers();
45
+
46
+ // Act
47
+ const result = await allHandler();
48
+
49
+ // Assert
50
+ expect( post ).toHaveBeenCalledWith( 'elementor/v1/mcp-proxy', {
51
+ tool: 'list-widget-schemas',
52
+ input: { summary: true },
53
+ } );
54
+ expect( JSON.parse( result.contents[ 0 ].text ) ).toEqual( [
55
+ { type: 'e-heading', description: 'A heading widget' },
56
+ { type: 'e-button', description: 'A button widget' },
57
+ ] );
58
+ } );
59
+
60
+ it( 'fetches v4 widgets via list-widget-schemas with summary=true', async () => {
61
+ // Arrange
62
+ const post = jest.fn().mockResolvedValue( {
63
+ data: { data: { widgets: [ { type: 'e-heading', description: 'A heading widget' } ] } },
64
+ } );
65
+ mockedHttpService.mockReturnValue( { post } as never );
66
+ const { v4Handler } = captureHandlers();
67
+
68
+ // Act
69
+ const result = await v4Handler();
70
+
71
+ // Assert
72
+ expect( post ).toHaveBeenCalledWith( 'elementor/v1/mcp-proxy', {
73
+ tool: 'list-widget-schemas',
74
+ input: { summary: true },
75
+ } );
76
+ expect( JSON.parse( result.contents[ 0 ].text ) ).toEqual( [
77
+ { type: 'e-heading', description: 'A heading widget' },
78
+ ] );
79
+ } );
80
+
81
+ it( 'exposes the expected resource URIs', () => {
82
+ expect( AVAILABLE_WIDGETS_URI ).toBe( 'elementor://context/available-widgets' );
83
+ expect( AVAILABLE_WIDGETS_URI_V4 ).toBe( 'elementor://context/available-widgets/v4' );
84
+ } );
85
+ } );
@@ -1,19 +1,12 @@
1
- import { Schema } from '@elementor/editor-props';
2
- import { getElementorConfig } from '@elementor/editor-v1-adapters';
1
+ import { httpService } from '@elementor/http-client';
3
2
 
4
3
  import { DYNAMIC_TAGS_URI, initDynamicTagsResource } from '../dynamic-tags-resource';
5
4
 
6
- jest.mock( '@elementor/editor-props', () => ( {
7
- Schema: {
8
- propTypeToJsonSchema: jest.fn( () => ( { mocked: true } ) ),
9
- },
5
+ jest.mock( '@elementor/http-client', () => ( {
6
+ httpService: jest.fn(),
10
7
  } ) );
11
8
 
12
- jest.mock( '@elementor/editor-v1-adapters', () => ( {
13
- getElementorConfig: jest.fn(),
14
- } ) );
15
-
16
- const mockedGetElementorConfig = getElementorConfig as jest.MockedFunction< typeof getElementorConfig >;
9
+ const mockedHttpService = httpService as jest.MockedFunction< typeof httpService >;
17
10
 
18
11
  type ResourceHandler = ( uri: URL ) => Promise< { contents: { text: string }[] } >;
19
12
 
@@ -34,52 +27,49 @@ describe( 'dynamic-tags-resource', () => {
34
27
  jest.clearAllMocks();
35
28
  } );
36
29
 
37
- it( 'returns a flat tag list with categories and omits the fallback setting', async () => {
30
+ it( 'returns the flat tag list fetched from the server', async () => {
38
31
  // Arrange
39
- mockedGetElementorConfig.mockReturnValue( {
40
- atomicDynamicTags: {
41
- tags: {
42
- 'post-custom-field': {
32
+ const post = jest.fn().mockResolvedValue( {
33
+ data: {
34
+ data: [
35
+ {
43
36
  name: 'post-custom-field',
44
37
  label: 'Post Custom Field',
45
- group: 'post',
46
38
  categories: [ 'text', 'url' ],
47
- props_schema: {
48
- key: { kind: 'string', key: 'string' },
49
- before: { kind: 'string', key: 'string' },
50
- fallback: { kind: 'string', key: 'string' },
51
- },
39
+ settings: { key: { mocked: true }, before: { mocked: true } },
52
40
  },
53
- },
54
- groups: { post: { title: 'Post' } },
41
+ ],
55
42
  },
56
- } as never );
43
+ } );
44
+ mockedHttpService.mockReturnValue( { post } as never );
57
45
 
58
46
  // Act
59
47
  const catalog = await readCatalog();
60
48
 
61
49
  // Assert
62
- expect( Array.isArray( catalog ) ).toBe( true );
63
- expect( catalog ).toHaveLength( 1 );
64
- expect( catalog[ 0 ] ).toMatchObject( {
65
- name: 'post-custom-field',
66
- label: 'Post Custom Field',
67
- categories: [ 'text', 'url' ],
50
+ expect( post ).toHaveBeenCalledWith( 'elementor/v1/mcp-proxy', {
51
+ tool: 'list-dynamic-tags',
52
+ input: {},
68
53
  } );
69
- expect( catalog[ 0 ] ).not.toHaveProperty( 'group' );
70
- expect( Object.keys( catalog[ 0 ].settings ) ).toEqual( [ 'key', 'before' ] );
71
- expect( catalog[ 0 ].settings ).not.toHaveProperty( 'fallback' );
54
+ expect( catalog ).toEqual( [
55
+ {
56
+ name: 'post-custom-field',
57
+ label: 'Post Custom Field',
58
+ categories: [ 'text', 'url' ],
59
+ settings: { key: { mocked: true }, before: { mocked: true } },
60
+ },
61
+ ] );
72
62
  } );
73
63
 
74
- it( 'returns an empty list when dynamic tags are unavailable', async () => {
64
+ it( 'returns an empty list when the server returns no data', async () => {
75
65
  // Arrange
76
- mockedGetElementorConfig.mockReturnValue( {} as never );
66
+ const post = jest.fn().mockResolvedValue( { data: {} } );
67
+ mockedHttpService.mockReturnValue( { post } as never );
77
68
 
78
69
  // Act
79
70
  const catalog = await readCatalog();
80
71
 
81
72
  // Assert
82
73
  expect( catalog ).toEqual( [] );
83
- expect( Schema.propTypeToJsonSchema ).not.toHaveBeenCalled();
84
74
  } );
85
75
  } );
@@ -1,95 +1,103 @@
1
- import { type V1ElementConfig } from '@elementor/editor-elements';
1
+ import { httpService } from '@elementor/http-client';
2
2
 
3
- import { buildLlmGuidance, enrichPropertiesWithBaseSettingsHints, mergeInstructions } from '../build-llm-guidance';
3
+ jest.mock( '@elementor/http-client', () => ( {
4
+ httpService: jest.fn(),
5
+ } ) );
4
6
 
5
- const mockEmailBaseSettings = {
6
- email: {
7
- $$type: 'emails',
8
- value: {
9
- to: {
10
- $$type: 'string-array',
11
- value: [ { $$type: 'string', value: 'admin@example.com' } ],
12
- },
13
- from: { $$type: 'string', value: 'email@example.com' },
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
- const mockFormWidgetData = {
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
- describe( 'build-llm-guidance', () => {
32
- it( 'mergeInstructions combines existing and additional instructions', () => {
33
- expect( mergeInstructions( 'First.', 'Second.' ) ).toBe( 'First. Second.' );
34
- expect( mergeInstructions( undefined, 'Only.' ) ).toBe( 'Only.' );
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
- it( 'buildLlmGuidance exposes default_settings for widgets with base_settings', () => {
38
- const guidance = buildLlmGuidance( mockFormWidgetData, 'e-form', {} );
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
- expect( guidance.default_settings ).toEqual( mockEmailBaseSettings );
41
- expect( guidance.instructions ).toContain( 'Omit them from elementConfig unless the user explicitly asks' );
42
- expect( guidance.can_have_children ).toBe( true );
38
+ describe( 'widgets-schema-resource', () => {
39
+ beforeEach( () => {
40
+ jest.clearAllMocks();
43
41
  } );
44
42
 
45
- it( 'buildLlmGuidance merges style and settings instructions when both exist', () => {
46
- const widgetData = {
47
- ...mockFormWidgetData,
48
- base_styles: {
49
- 'e-form-base': {
50
- variants: [
51
- {
52
- props: {
53
- display: { $$type: 'string', value: 'flex' },
54
- },
55
- },
43
+ it( 'lists widget types fetched from the server via list-widget-schemas with summary=true', async () => {
44
+ // Arrange
45
+ const post = jest.fn().mockResolvedValue( {
46
+ data: {
47
+ data: {
48
+ widgets: [
49
+ { type: 'e-heading', description: 'A heading widget' },
50
+ { type: 'e-button', description: 'A button widget' },
56
51
  ],
57
52
  },
58
53
  },
59
- } as unknown as V1ElementConfig;
54
+ } );
55
+ mockedHttpService.mockReturnValue( { post } as never );
56
+ const { list } = captureHandlers();
60
57
 
61
- const guidance = buildLlmGuidance( widgetData, 'e-form', {} );
58
+ // Act
59
+ const result = await list();
62
60
 
63
- expect( guidance.default_styles ).toEqual( { display: { $$type: 'string', value: 'flex' } } );
64
- expect( guidance.default_settings ).toEqual( mockEmailBaseSettings );
65
- expect( guidance.instructions ).toContain( 'default styles' );
66
- expect( guidance.instructions ).toContain( 'default settings' );
61
+ // Assert
62
+ expect( post ).toHaveBeenCalledWith( 'elementor/v1/mcp-proxy', {
63
+ tool: 'list-widget-schemas',
64
+ input: { summary: true },
65
+ } );
66
+ expect( result.resources ).toEqual( [
67
+ { uri: 'elementor://widgets/schema/e-heading', name: 'Widget schema for e-heading' },
68
+ { uri: 'elementor://widgets/schema/e-button', name: 'Widget schema for e-button' },
69
+ ] );
67
70
  } );
68
71
 
69
- it( 'enrichPropertiesWithBaseSettingsHints adds omit guidance to base setting props', () => {
70
- const enriched = enrichPropertiesWithBaseSettingsHints(
71
- {
72
- email: { type: 'object' },
73
- 'form-name': { type: 'object' },
74
- },
75
- [ 'email' ]
76
- );
72
+ it( 'reads a single widget schema via the get-widget-schema tool', async () => {
73
+ // Arrange
74
+ const post = jest.fn().mockResolvedValue( {
75
+ data: { data: { type: 'object', properties: { text: { mocked: true } } } },
76
+ } );
77
+ mockedHttpService.mockReturnValue( { post } as never );
78
+ const { readHandler } = captureHandlers();
79
+ const uri = new URL( 'elementor://widgets/schema/e-heading' );
80
+
81
+ // Act
82
+ const result = await readHandler( uri, { widgetType: 'e-heading' } );
77
83
 
78
- expect( enriched.email.description ).toContain( 'llm_guidance.default_settings' );
79
- expect( enriched[ 'form-name' ].description ).toBeUndefined();
84
+ // Assert
85
+ expect( post ).toHaveBeenCalledWith( 'elementor/v1/mcp-proxy', {
86
+ tool: 'get-widget-schema',
87
+ input: { widget_type: 'e-heading' },
88
+ } );
89
+ expect( JSON.parse( result.contents[ 0 ].text ) ).toEqual( {
90
+ type: 'object',
91
+ properties: { text: { mocked: true } },
92
+ } );
80
93
  } );
81
- } );
82
94
 
83
- describe( 'widgets-schema-resource base_settings integration', () => {
84
- it( 'keeps base setting props in schema while enriching descriptions', () => {
85
- const properties = enrichPropertiesWithBaseSettingsHints(
86
- {
87
- email: { type: 'object', properties: {} },
88
- },
89
- Object.keys( mockEmailBaseSettings )
90
- );
95
+ it( 'throws when no widget type variable is provided', async () => {
96
+ // Arrange
97
+ const { readHandler } = captureHandlers();
98
+ const uri = new URL( WIDGET_SCHEMA_URI.replace( '{widgetType}', '' ) );
91
99
 
92
- expect( properties ).toHaveProperty( 'email' );
93
- expect( properties.email.description ).toContain( 'omit unless user explicitly requests' );
100
+ // Act & Assert
101
+ await expect( readHandler( uri, {} ) ).rejects.toThrow( 'No widget type provided.' );
94
102
  } );
95
103
  } );
@@ -1,37 +1,45 @@
1
1
  import { type MCPRegistryEntry } from '@elementor/editor-mcp';
2
- import { v1ReadyEvent } from '@elementor/editor-v1-adapters';
2
+ import { type HttpResponse, httpService } from '@elementor/http-client';
3
3
 
4
- import { type AvailableWidget, getAvailableWidgets } from '../utils/element-data-util';
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
- export const initAvailableWidgetsResource = ( reg: MCPRegistryEntry ) => {
10
- const { resource, sendResourceUpdated } = reg;
9
+ type WidgetSummary = {
10
+ type: string;
11
+ description?: string;
12
+ };
11
13
 
12
- const buildContents = ( uri: string, filterFunction: ( x: AvailableWidget ) => boolean = () => true ) => {
13
- const widgets = getAvailableWidgets().filter( filterFunction );
14
- return {
15
- contents: [
16
- {
17
- uri,
18
- mimeType: 'application/json',
19
- text: JSON.stringify( widgets, null, 2 ),
20
- },
21
- ],
22
- };
23
- };
14
+ type WidgetSummaryResponse = {
15
+ widgets: WidgetSummary[];
16
+ };
17
+
18
+ const fetchWidgets = async (): Promise< WidgetSummary[] > => {
19
+ const { data } = await httpService().post< HttpResponse< WidgetSummaryResponse > >( MCP_PROXY_URL, {
20
+ tool: 'list-widget-schemas',
21
+ input: { summary: true },
22
+ } );
23
+
24
+ return data.data?.widgets ?? [];
25
+ };
24
26
 
25
- const notifyResourcesUpdated = () => {
26
- sendResourceUpdated( {
27
- uri: AVAILABLE_WIDGETS_URI,
28
- ...buildContents( AVAILABLE_WIDGETS_URI ),
29
- } );
30
- sendResourceUpdated( {
31
- uri: AVAILABLE_WIDGETS_URI_V4,
32
- ...buildContents( AVAILABLE_WIDGETS_URI_V4, ( w: AvailableWidget ) => w.version === 'v4' ),
33
- } );
27
+ const buildContents = async ( uri: string ) => {
28
+ const widgets = await fetchWidgets();
29
+
30
+ return {
31
+ contents: [
32
+ {
33
+ uri,
34
+ mimeType: 'application/json',
35
+ text: JSON.stringify( widgets, null, 2 ),
36
+ },
37
+ ],
34
38
  };
39
+ };
40
+
41
+ export const initAvailableWidgetsResource = ( reg: MCPRegistryEntry ) => {
42
+ const { resource } = reg;
35
43
 
36
44
  resource(
37
45
  'available-widgets-v4',
@@ -39,29 +47,15 @@ export const initAvailableWidgetsResource = ( reg: MCPRegistryEntry ) => {
39
47
  {
40
48
  description: 'All registered v4 version widgets',
41
49
  },
42
- async () => buildContents( AVAILABLE_WIDGETS_URI_V4, ( w ) => w.version === 'v4' )
50
+ async () => buildContents( AVAILABLE_WIDGETS_URI_V4 )
43
51
  );
44
52
 
45
53
  resource(
46
54
  'available-widgets',
47
55
  AVAILABLE_WIDGETS_URI,
48
56
  {
49
- description: 'All registered widget types with v3/v4 version metadata and description.',
57
+ description: 'All registered v4 widget types with description.',
50
58
  },
51
59
  async () => buildContents( AVAILABLE_WIDGETS_URI )
52
60
  );
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
61
  };
@@ -0,0 +1,34 @@
1
+ import { type MCPRegistryEntry } from '@elementor/editor-mcp';
2
+ import { type HttpResponse, httpService } from '@elementor/http-client';
3
+
4
+ const MCP_PROXY_URL = 'elementor/v1/mcp-proxy';
5
+ export const BEST_PRACTICES_URI = 'elementor://style/best-practices';
6
+
7
+ export const initBestPracticesResource = ( reg: MCPRegistryEntry ) => {
8
+ const { resource } = reg;
9
+
10
+ resource(
11
+ 'style-best-practices',
12
+ BEST_PRACTICES_URI,
13
+ {
14
+ description:
15
+ 'Design quality guidelines for avoiding generic AI output: typography, color strategy, spacing, motion, and visual hierarchy best practices.',
16
+ mimeType: 'text/markdown',
17
+ },
18
+ async ( uri: URL ) => {
19
+ const { data } = await httpService().get< HttpResponse< string > >( MCP_PROXY_URL, {
20
+ params: { uri: uri.href },
21
+ } );
22
+
23
+ return {
24
+ contents: [
25
+ {
26
+ uri: uri.href,
27
+ mimeType: 'text/markdown',
28
+ text: data.data,
29
+ },
30
+ ],
31
+ };
32
+ }
33
+ );
34
+ };