@elementor/editor-canvas 4.3.0-1003 → 4.3.0-1005

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 CHANGED
@@ -4522,6 +4522,9 @@ var initBuildCompositionTool = (reg) => {
4522
4522
  "Map configuration-id \u2192 raw CSS declarations (property \u2192 value strings; no selectors). Server converts to native styles; unconvertible declarations become the element custom CSS."
4523
4523
  ),
4524
4524
  parentId: import_schema.z.string().optional().describe("ID of the parent container. Omit or pass 'document' to insert at document root."),
4525
+ mode: import_schema.z.enum(["append", "replace_children"]).optional().describe(
4526
+ "'append' (default) inserts under parentId; 'replace_children' removes existing direct children of parentId first, then inserts."
4527
+ ),
4525
4528
  dryRun: import_schema.z.boolean().optional().describe("If true, validate and return the resolved tree without persisting.")
4526
4529
  },
4527
4530
  outputSchema: {
@@ -4530,9 +4533,10 @@ var initBuildCompositionTool = (reg) => {
4530
4533
  version: import_schema.z.string(),
4531
4534
  resolvedXml: import_schema.z.string(),
4532
4535
  llmInstructions: import_schema.z.string(),
4533
- warnings: import_schema.z.array(import_schema.z.string()).optional()
4536
+ warnings: import_schema.z.array(import_schema.z.string()).optional(),
4537
+ removedElementIds: import_schema.z.array(import_schema.z.string()).optional()
4534
4538
  },
4535
- handler: async ({ xmlStructure, elementConfig, style, parentId, dryRun }) => {
4539
+ handler: async ({ xmlStructure, elementConfig, style, parentId, mode, dryRun }) => {
4536
4540
  const document2 = (0, import_editor_documents2.getCurrentDocument)();
4537
4541
  if (!document2?.id) {
4538
4542
  throw new Error("No active document found.");
@@ -4546,6 +4550,7 @@ var initBuildCompositionTool = (reg) => {
4546
4550
  element_config: elementConfig ?? {},
4547
4551
  style: style ?? {},
4548
4552
  parent_id: parentId ?? "document",
4553
+ mode: mode ?? "append",
4549
4554
  dry_run: dryRun ?? false
4550
4555
  }
4551
4556
  });
@@ -4566,7 +4571,8 @@ var initBuildCompositionTool = (reg) => {
4566
4571
  version: data.data.version,
4567
4572
  resolvedXml: data.data.resolved_xml,
4568
4573
  llmInstructions: data.data.llm_instructions,
4569
- warnings: data.data.warnings
4574
+ warnings: data.data.warnings,
4575
+ removedElementIds: data.data.removed_element_ids
4570
4576
  };
4571
4577
  } catch (error) {
4572
4578
  throw new Error(getMcpErrorMessage(error, "build-composition"));
@@ -4719,6 +4725,20 @@ var LOCAL_STYLE_META = {
4719
4725
  breakpoint: "desktop",
4720
4726
  state: null
4721
4727
  };
4728
+ var UnsupportedPropertyError = class extends Error {
4729
+ elementType;
4730
+ propertyName;
4731
+ constructor(elementType, propertyName, availableProperties) {
4732
+ super(
4733
+ `Property "${propertyName}" does not exist on element type "${elementType}". Available properties are: ${availableProperties.join(
4734
+ ", "
4735
+ )}`
4736
+ );
4737
+ this.name = "UnsupportedPropertyError";
4738
+ this.elementType = elementType;
4739
+ this.propertyName = propertyName;
4740
+ }
4741
+ };
4722
4742
  function resolvePropValue(value, forceKey) {
4723
4743
  const Utils = window.elementorV2.editorVariables.Utils;
4724
4744
  return import_editor_props6.Schema.adjustLlmPropValueSchema(value, {
@@ -4824,12 +4844,7 @@ var doUpdateElementProperty = (params) => {
4824
4844
  throw new Error(`No prop schema found for element type: ${elementType}`);
4825
4845
  }
4826
4846
  if (!elementPropSchema[propertyName]) {
4827
- const propertyNames = Object.keys(elementPropSchema);
4828
- throw new Error(
4829
- `Property "${propertyName}" does not exist on element type "${elementType}". Available properties are: ${propertyNames.join(
4830
- ", "
4831
- )}`
4832
- );
4847
+ throw new UnsupportedPropertyError(elementType, propertyName, Object.keys(elementPropSchema));
4833
4848
  }
4834
4849
  const propKey = elementPropSchema[propertyName].key;
4835
4850
  const value = resolvePropValue(propertyValue, propKey);
@@ -4959,12 +4974,12 @@ Do NOT send "group" (it is resolved automatically). Use { "settings": {} } only
4959
4974
  V4 only: If MCP fails, give manual steps using V4 UI.
4960
4975
 
4961
4976
  V4 Editor structure:
4962
- Panel tabs: General (\u2192 Settings section: ID, Tag, Link), Style, Interactions.
4977
+ Panel tabs: General (\u2192 Settings section: ID, Tag, and Link where the widget supports it), Style, Interactions.
4963
4978
  NO Advanced tab. Never mention Advanced tab.
4979
+ Note: \`link\` is valid only when the element's PropType schema (which you must already have) includes a \`link\` property. Sending \`link\` to a widget whose schema lacks it is skipped and reported in the response \`warnings\` (other changes still apply) and the link is lost.
4964
4980
  `);
4965
4981
  return configureElementToolPrompt.prompt();
4966
4982
  };
4967
- var CONFIGURE_ELEMENT_GUIDE_TEXT = generatePrompt();
4968
4983
 
4969
4984
  // src/mcp/tools/configure-element/schema.ts
4970
4985
  var import_schema2 = require("@elementor/schema");
@@ -4988,7 +5003,10 @@ var inputSchema = {
4988
5003
  var outputSchema = {
4989
5004
  success: import_schema2.z.boolean().describe(
4990
5005
  "Whether the configuration change was successful, only if propertyName and propertyValue are provided"
4991
- )
5006
+ ),
5007
+ warnings: import_schema2.z.string().describe(
5008
+ 'Non-fatal notices. Present when some props were skipped because they are not in the element schema (e.g. a "link" on a widget with no link prop). Other changes were still applied.'
5009
+ ).optional()
4992
5010
  };
4993
5011
 
4994
5012
  // src/mcp/tools/configure-element/tool.ts
@@ -5038,6 +5056,7 @@ var initConfigureElementTool = (reg) => {
5038
5056
  }
5039
5057
  const propertiesToUpdate = resolveCanonicalPropKeys(elementType, propertiesToChange);
5040
5058
  const toUpdate = Object.entries(propertiesToUpdate);
5059
+ const skippedProps = [];
5041
5060
  for (const [propertyName, propertyValue] of toUpdate) {
5042
5061
  if (!import_editor_props7.Schema.isPropKeyConfigurable(propertyName)) {
5043
5062
  throw new Error(`Not allowed to update ${propertyName}`);
@@ -5050,6 +5069,10 @@ var initConfigureElementTool = (reg) => {
5050
5069
  propertyValue
5051
5070
  });
5052
5071
  } catch (error) {
5072
+ if (error instanceof UnsupportedPropertyError) {
5073
+ skippedProps.push(error.propertyName);
5074
+ continue;
5075
+ }
5053
5076
  const errorMessage = createUpdateErrorMessage({
5054
5077
  propertyName,
5055
5078
  elementId,
@@ -5062,7 +5085,10 @@ var initConfigureElementTool = (reg) => {
5062
5085
  }
5063
5086
  await applyStyleFromCss({ elementId, elementType, style });
5064
5087
  return {
5065
- success: true
5088
+ success: true,
5089
+ warnings: skippedProps.length ? `Skipped unsupported props (not in the "${elementType}" schema; other changes were applied): ${skippedProps.join(
5090
+ ", "
5091
+ )}.` : void 0
5066
5092
  };
5067
5093
  }
5068
5094
  });
package/dist/index.mjs CHANGED
@@ -4488,6 +4488,9 @@ var initBuildCompositionTool = (reg) => {
4488
4488
  "Map configuration-id \u2192 raw CSS declarations (property \u2192 value strings; no selectors). Server converts to native styles; unconvertible declarations become the element custom CSS."
4489
4489
  ),
4490
4490
  parentId: z.string().optional().describe("ID of the parent container. Omit or pass 'document' to insert at document root."),
4491
+ mode: z.enum(["append", "replace_children"]).optional().describe(
4492
+ "'append' (default) inserts under parentId; 'replace_children' removes existing direct children of parentId first, then inserts."
4493
+ ),
4491
4494
  dryRun: z.boolean().optional().describe("If true, validate and return the resolved tree without persisting.")
4492
4495
  },
4493
4496
  outputSchema: {
@@ -4496,9 +4499,10 @@ var initBuildCompositionTool = (reg) => {
4496
4499
  version: z.string(),
4497
4500
  resolvedXml: z.string(),
4498
4501
  llmInstructions: z.string(),
4499
- warnings: z.array(z.string()).optional()
4502
+ warnings: z.array(z.string()).optional(),
4503
+ removedElementIds: z.array(z.string()).optional()
4500
4504
  },
4501
- handler: async ({ xmlStructure, elementConfig, style, parentId, dryRun }) => {
4505
+ handler: async ({ xmlStructure, elementConfig, style, parentId, mode, dryRun }) => {
4502
4506
  const document2 = getCurrentDocument();
4503
4507
  if (!document2?.id) {
4504
4508
  throw new Error("No active document found.");
@@ -4512,6 +4516,7 @@ var initBuildCompositionTool = (reg) => {
4512
4516
  element_config: elementConfig ?? {},
4513
4517
  style: style ?? {},
4514
4518
  parent_id: parentId ?? "document",
4519
+ mode: mode ?? "append",
4515
4520
  dry_run: dryRun ?? false
4516
4521
  }
4517
4522
  });
@@ -4532,7 +4537,8 @@ var initBuildCompositionTool = (reg) => {
4532
4537
  version: data.data.version,
4533
4538
  resolvedXml: data.data.resolved_xml,
4534
4539
  llmInstructions: data.data.llm_instructions,
4535
- warnings: data.data.warnings
4540
+ warnings: data.data.warnings,
4541
+ removedElementIds: data.data.removed_element_ids
4536
4542
  };
4537
4543
  } catch (error) {
4538
4544
  throw new Error(getMcpErrorMessage(error, "build-composition"));
@@ -4691,6 +4697,20 @@ var LOCAL_STYLE_META = {
4691
4697
  breakpoint: "desktop",
4692
4698
  state: null
4693
4699
  };
4700
+ var UnsupportedPropertyError = class extends Error {
4701
+ elementType;
4702
+ propertyName;
4703
+ constructor(elementType, propertyName, availableProperties) {
4704
+ super(
4705
+ `Property "${propertyName}" does not exist on element type "${elementType}". Available properties are: ${availableProperties.join(
4706
+ ", "
4707
+ )}`
4708
+ );
4709
+ this.name = "UnsupportedPropertyError";
4710
+ this.elementType = elementType;
4711
+ this.propertyName = propertyName;
4712
+ }
4713
+ };
4694
4714
  function resolvePropValue(value, forceKey) {
4695
4715
  const Utils = window.elementorV2.editorVariables.Utils;
4696
4716
  return Schema.adjustLlmPropValueSchema(value, {
@@ -4796,12 +4816,7 @@ var doUpdateElementProperty = (params) => {
4796
4816
  throw new Error(`No prop schema found for element type: ${elementType}`);
4797
4817
  }
4798
4818
  if (!elementPropSchema[propertyName]) {
4799
- const propertyNames = Object.keys(elementPropSchema);
4800
- throw new Error(
4801
- `Property "${propertyName}" does not exist on element type "${elementType}". Available properties are: ${propertyNames.join(
4802
- ", "
4803
- )}`
4804
- );
4819
+ throw new UnsupportedPropertyError(elementType, propertyName, Object.keys(elementPropSchema));
4805
4820
  }
4806
4821
  const propKey = elementPropSchema[propertyName].key;
4807
4822
  const value = resolvePropValue(propertyValue, propKey);
@@ -4931,12 +4946,12 @@ Do NOT send "group" (it is resolved automatically). Use { "settings": {} } only
4931
4946
  V4 only: If MCP fails, give manual steps using V4 UI.
4932
4947
 
4933
4948
  V4 Editor structure:
4934
- Panel tabs: General (\u2192 Settings section: ID, Tag, Link), Style, Interactions.
4949
+ Panel tabs: General (\u2192 Settings section: ID, Tag, and Link where the widget supports it), Style, Interactions.
4935
4950
  NO Advanced tab. Never mention Advanced tab.
4951
+ Note: \`link\` is valid only when the element's PropType schema (which you must already have) includes a \`link\` property. Sending \`link\` to a widget whose schema lacks it is skipped and reported in the response \`warnings\` (other changes still apply) and the link is lost.
4936
4952
  `);
4937
4953
  return configureElementToolPrompt.prompt();
4938
4954
  };
4939
- var CONFIGURE_ELEMENT_GUIDE_TEXT = generatePrompt();
4940
4955
 
4941
4956
  // src/mcp/tools/configure-element/schema.ts
4942
4957
  import { z as z2 } from "@elementor/schema";
@@ -4960,7 +4975,10 @@ var inputSchema = {
4960
4975
  var outputSchema = {
4961
4976
  success: z2.boolean().describe(
4962
4977
  "Whether the configuration change was successful, only if propertyName and propertyValue are provided"
4963
- )
4978
+ ),
4979
+ warnings: z2.string().describe(
4980
+ 'Non-fatal notices. Present when some props were skipped because they are not in the element schema (e.g. a "link" on a widget with no link prop). Other changes were still applied.'
4981
+ ).optional()
4964
4982
  };
4965
4983
 
4966
4984
  // src/mcp/tools/configure-element/tool.ts
@@ -5010,6 +5028,7 @@ var initConfigureElementTool = (reg) => {
5010
5028
  }
5011
5029
  const propertiesToUpdate = resolveCanonicalPropKeys(elementType, propertiesToChange);
5012
5030
  const toUpdate = Object.entries(propertiesToUpdate);
5031
+ const skippedProps = [];
5013
5032
  for (const [propertyName, propertyValue] of toUpdate) {
5014
5033
  if (!Schema2.isPropKeyConfigurable(propertyName)) {
5015
5034
  throw new Error(`Not allowed to update ${propertyName}`);
@@ -5022,6 +5041,10 @@ var initConfigureElementTool = (reg) => {
5022
5041
  propertyValue
5023
5042
  });
5024
5043
  } catch (error) {
5044
+ if (error instanceof UnsupportedPropertyError) {
5045
+ skippedProps.push(error.propertyName);
5046
+ continue;
5047
+ }
5025
5048
  const errorMessage = createUpdateErrorMessage({
5026
5049
  propertyName,
5027
5050
  elementId,
@@ -5034,7 +5057,10 @@ var initConfigureElementTool = (reg) => {
5034
5057
  }
5035
5058
  await applyStyleFromCss({ elementId, elementType, style });
5036
5059
  return {
5037
- success: true
5060
+ success: true,
5061
+ warnings: skippedProps.length ? `Skipped unsupported props (not in the "${elementType}" schema; other changes were applied): ${skippedProps.join(
5062
+ ", "
5063
+ )}.` : void 0
5038
5064
  };
5039
5065
  }
5040
5066
  });
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-1003",
4
+ "version": "4.3.0-1005",
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-1003",
41
- "@elementor/editor-controls": "4.3.0-1003",
42
- "@elementor/editor-documents": "4.3.0-1003",
43
- "@elementor/editor-elements": "4.3.0-1003",
44
- "@elementor/editor-interactions": "4.3.0-1003",
45
- "@elementor/editor-mcp": "4.3.0-1003",
46
- "@elementor/editor-notifications": "4.3.0-1003",
47
- "@elementor/editor-props": "4.3.0-1003",
48
- "@elementor/editor-responsive": "4.3.0-1003",
49
- "@elementor/editor-styles": "4.3.0-1003",
50
- "@elementor/editor-styles-repository": "4.3.0-1003",
51
- "@elementor/editor-ui": "4.3.0-1003",
52
- "@elementor/editor-v1-adapters": "4.3.0-1003",
53
- "@elementor/events": "4.3.0-1003",
54
- "@elementor/http-client": "4.3.0-1003",
55
- "@elementor/schema": "4.3.0-1003",
56
- "@elementor/twing": "4.3.0-1003",
40
+ "@elementor/editor": "4.3.0-1005",
41
+ "@elementor/editor-controls": "4.3.0-1005",
42
+ "@elementor/editor-documents": "4.3.0-1005",
43
+ "@elementor/editor-elements": "4.3.0-1005",
44
+ "@elementor/editor-interactions": "4.3.0-1005",
45
+ "@elementor/editor-mcp": "4.3.0-1005",
46
+ "@elementor/editor-notifications": "4.3.0-1005",
47
+ "@elementor/editor-props": "4.3.0-1005",
48
+ "@elementor/editor-responsive": "4.3.0-1005",
49
+ "@elementor/editor-styles": "4.3.0-1005",
50
+ "@elementor/editor-styles-repository": "4.3.0-1005",
51
+ "@elementor/editor-ui": "4.3.0-1005",
52
+ "@elementor/editor-v1-adapters": "4.3.0-1005",
53
+ "@elementor/events": "4.3.0-1005",
54
+ "@elementor/http-client": "4.3.0-1005",
55
+ "@elementor/schema": "4.3.0-1005",
56
+ "@elementor/twing": "4.3.0-1005",
57
57
  "@elementor/ui": "1.37.5",
58
- "@elementor/utils": "4.3.0-1003",
59
- "@elementor/wp-media": "4.3.0-1003",
58
+ "@elementor/utils": "4.3.0-1005",
59
+ "@elementor/wp-media": "4.3.0-1005",
60
60
  "@floating-ui/react": "^0.27.5",
61
61
  "@wordpress/i18n": "^5.13.0",
62
62
  "dompurify": "^3.2.6"
@@ -17,6 +17,7 @@ type BuildCompositionResponse = {
17
17
  resolved_xml: string;
18
18
  llm_instructions: string;
19
19
  warnings?: string[];
20
+ removed_element_ids?: string[];
20
21
  };
21
22
 
22
23
  export const initBuildCompositionTool = ( reg: MCPRegistryEntry ) => {
@@ -58,6 +59,12 @@ export const initBuildCompositionTool = ( reg: MCPRegistryEntry ) => {
58
59
  .string()
59
60
  .optional()
60
61
  .describe( "ID of the parent container. Omit or pass 'document' to insert at document root." ),
62
+ mode: z
63
+ .enum( [ 'append', 'replace_children' ] )
64
+ .optional()
65
+ .describe(
66
+ "'append' (default) inserts under parentId; 'replace_children' removes existing direct children of parentId first, then inserts."
67
+ ),
61
68
  dryRun: z
62
69
  .boolean()
63
70
  .optional()
@@ -70,8 +77,9 @@ export const initBuildCompositionTool = ( reg: MCPRegistryEntry ) => {
70
77
  resolvedXml: z.string(),
71
78
  llmInstructions: z.string(),
72
79
  warnings: z.array( z.string() ).optional(),
80
+ removedElementIds: z.array( z.string() ).optional(),
73
81
  },
74
- handler: async ( { xmlStructure, elementConfig, style, parentId, dryRun } ) => {
82
+ handler: async ( { xmlStructure, elementConfig, style, parentId, mode, dryRun } ) => {
75
83
  const document = getCurrentDocument();
76
84
 
77
85
  if ( ! document?.id ) {
@@ -87,6 +95,7 @@ export const initBuildCompositionTool = ( reg: MCPRegistryEntry ) => {
87
95
  element_config: elementConfig ?? {},
88
96
  style: style ?? {},
89
97
  parent_id: parentId ?? 'document',
98
+ mode: mode ?? 'append',
90
99
  dry_run: dryRun ?? false,
91
100
  },
92
101
  } );
@@ -111,6 +120,7 @@ export const initBuildCompositionTool = ( reg: MCPRegistryEntry ) => {
111
120
  resolvedXml: data.data.resolved_xml,
112
121
  llmInstructions: data.data.llm_instructions,
113
122
  warnings: data.data.warnings,
123
+ removedElementIds: data.data.removed_element_ids,
114
124
  };
115
125
  } catch ( error ) {
116
126
  throw new Error( getMcpErrorMessage( error, 'build-composition' ) );
@@ -0,0 +1,130 @@
1
+ import { getContainer, getWidgetsCache } from '@elementor/editor-elements';
2
+
3
+ import { doUpdateElementProperty, UnsupportedPropertyError } from '../../../utils/do-update-element-property';
4
+ import { initConfigureElementTool } from '../tool';
5
+
6
+ jest.mock( '@elementor/editor-elements', () => ( {
7
+ getContainer: jest.fn(),
8
+ getWidgetsCache: jest.fn(),
9
+ } ) );
10
+
11
+ jest.mock( '@elementor/editor-mcp', () => ( {
12
+ dispatchMcpStylesAppliedEvent: jest.fn(),
13
+ } ) );
14
+
15
+ jest.mock( '@elementor/editor-props', () => ( {
16
+ Schema: { isPropKeyConfigurable: () => true },
17
+ } ) );
18
+
19
+ jest.mock( '../prompt', () => ( {
20
+ CONFIGURE_ELEMENT_GUIDE_URI: 'configure-element-guide-uri',
21
+ generatePrompt: () => '',
22
+ } ) );
23
+
24
+ jest.mock( '../../../resources/widgets-schema-resource', () => ( {
25
+ WIDGET_SCHEMA_URI: 'elementor://widgets/schema/{widgetType}',
26
+ } ) );
27
+
28
+ jest.mock( '../../../resources/dynamic-tags-resource', () => ( {
29
+ DYNAMIC_TAGS_URI: 'elementor://dynamic-tags',
30
+ } ) );
31
+
32
+ jest.mock( '../../../utils/resolve-canonical-prop-name', () => ( {
33
+ resolveCanonicalPropKeys: ( _elementType: string, props: Record< string, unknown > ) => props,
34
+ } ) );
35
+
36
+ jest.mock( '../../../utils/convert-css-to-atomic', () => ( {
37
+ convertCssToAtomic: jest.fn().mockResolvedValue( { props: {}, customCss: '' } ),
38
+ } ) );
39
+
40
+ jest.mock( '../../../utils/do-update-element-property', () => ( {
41
+ ...jest.requireActual( '../../../utils/do-update-element-property' ),
42
+ doUpdateElementProperty: jest.fn(),
43
+ } ) );
44
+
45
+ const ELEMENT_ID = 'el-1';
46
+ const ELEMENT_TYPE = 'e-youtube';
47
+
48
+ type Handler = ( params: {
49
+ elementId: string;
50
+ elementType: string;
51
+ propertiesToChange: Record< string, unknown >;
52
+ style: Record< string, string | null >;
53
+ } ) => Promise< { success: boolean; warnings?: string } >;
54
+
55
+ const getHandler = (): Handler => {
56
+ const addTool = jest.fn();
57
+ initConfigureElementTool( { addTool, resource: jest.fn() } as never );
58
+ return addTool.mock.calls[ 0 ][ 0 ].handler;
59
+ };
60
+
61
+ describe( 'configure-element handler unsupported props', () => {
62
+ beforeEach( () => {
63
+ jest.mocked( getWidgetsCache ).mockReturnValue( {
64
+ [ ELEMENT_TYPE ]: { atomic_props_schema: { title: { key: 'title' } } },
65
+ } as never );
66
+ jest.mocked( getContainer ).mockReturnValue( {
67
+ settings: { get: () => ELEMENT_TYPE },
68
+ } as never );
69
+ jest.mocked( doUpdateElementProperty ).mockReset();
70
+ } );
71
+
72
+ it( 'skips an unsupported prop, reports it in warnings, and still applies supported props', async () => {
73
+ // Arrange
74
+ jest.mocked( doUpdateElementProperty ).mockImplementation( ( { propertyName } ) => {
75
+ if ( 'link' === propertyName ) {
76
+ throw new UnsupportedPropertyError( ELEMENT_TYPE, 'link', [ 'title' ] );
77
+ }
78
+ } );
79
+ const handler = getHandler();
80
+
81
+ // Act
82
+ const result = await handler( {
83
+ elementId: ELEMENT_ID,
84
+ elementType: ELEMENT_TYPE,
85
+ propertiesToChange: { title: { $$type: 'string', value: 'Hi' }, link: { $$type: 'link', value: {} } },
86
+ style: {},
87
+ } );
88
+
89
+ // Assert
90
+ expect( result.success ).toBe( true );
91
+ expect( result.warnings ).toContain( 'link' );
92
+ expect( doUpdateElementProperty ).toHaveBeenCalledTimes( 2 );
93
+ } );
94
+
95
+ it( 'returns no warnings when every prop is supported', async () => {
96
+ // Arrange
97
+ jest.mocked( doUpdateElementProperty ).mockImplementation( () => undefined );
98
+ const handler = getHandler();
99
+
100
+ // Act
101
+ const result = await handler( {
102
+ elementId: ELEMENT_ID,
103
+ elementType: ELEMENT_TYPE,
104
+ propertiesToChange: { title: { $$type: 'string', value: 'Hi' } },
105
+ style: {},
106
+ } );
107
+
108
+ // Assert
109
+ expect( result.success ).toBe( true );
110
+ expect( result.warnings ).toBeUndefined();
111
+ } );
112
+
113
+ it( 'rethrows non-UnsupportedPropertyError failures', async () => {
114
+ // Arrange
115
+ jest.mocked( doUpdateElementProperty ).mockImplementation( () => {
116
+ throw new Error( 'Invalid PropValue' );
117
+ } );
118
+ const handler = getHandler();
119
+
120
+ // Act & Assert
121
+ await expect(
122
+ handler( {
123
+ elementId: ELEMENT_ID,
124
+ elementType: ELEMENT_TYPE,
125
+ propertiesToChange: { title: { $$type: 'string', value: 'Hi' } },
126
+ style: {},
127
+ } )
128
+ ).rejects.toThrow( /Invalid PropValue/ );
129
+ } );
130
+ } );
@@ -117,11 +117,10 @@ Do NOT send "group" (it is resolved automatically). Use { "settings": {} } only
117
117
  V4 only: If MCP fails, give manual steps using V4 UI.
118
118
 
119
119
  V4 Editor structure:
120
- Panel tabs: General (→ Settings section: ID, Tag, Link), Style, Interactions.
120
+ Panel tabs: General (→ Settings section: ID, Tag, and Link where the widget supports it), Style, Interactions.
121
121
  NO Advanced tab. Never mention Advanced tab.
122
+ Note: \`link\` is valid only when the element's PropType schema (which you must already have) includes a \`link\` property. Sending \`link\` to a widget whose schema lacks it is skipped and reported in the response \`warnings\` (other changes still apply) and the link is lost.
122
123
  ` );
123
124
 
124
125
  return configureElementToolPrompt.prompt();
125
126
  };
126
-
127
- export const CONFIGURE_ELEMENT_GUIDE_TEXT = generatePrompt();
@@ -36,4 +36,10 @@ export const outputSchema = {
36
36
  .describe(
37
37
  'Whether the configuration change was successful, only if propertyName and propertyValue are provided'
38
38
  ),
39
+ warnings: z
40
+ .string()
41
+ .describe(
42
+ 'Non-fatal notices. Present when some props were skipped because they are not in the element schema (e.g. a "link" on a widget with no link prop). Other changes were still applied.'
43
+ )
44
+ .optional(),
39
45
  };
@@ -6,7 +6,7 @@ import { type PropValue, Schema } from '@elementor/editor-props';
6
6
  import { DYNAMIC_TAGS_URI } from '../../resources/dynamic-tags-resource';
7
7
  import { WIDGET_SCHEMA_URI } from '../../resources/widgets-schema-resource';
8
8
  import { convertCssToAtomic } from '../../utils/convert-css-to-atomic';
9
- import { doUpdateElementProperty } from '../../utils/do-update-element-property';
9
+ import { doUpdateElementProperty, UnsupportedPropertyError } from '../../utils/do-update-element-property';
10
10
  import { resolveCanonicalPropKeys } from '../../utils/resolve-canonical-prop-name';
11
11
  import { CONFIGURE_ELEMENT_GUIDE_URI, generatePrompt } from './prompt';
12
12
  import { inputSchema as schema, outputSchema } from './schema';
@@ -61,6 +61,7 @@ export const initConfigureElementTool = ( reg: MCPRegistryEntry ) => {
61
61
  }
62
62
  const propertiesToUpdate = resolveCanonicalPropKeys( elementType, propertiesToChange );
63
63
  const toUpdate = Object.entries( propertiesToUpdate );
64
+ const skippedProps: string[] = [];
64
65
  for ( const [ propertyName, propertyValue ] of toUpdate as [ string, PropValue ][] ) {
65
66
  if ( ! Schema.isPropKeyConfigurable( propertyName ) ) {
66
67
  throw new Error( `Not allowed to update ${ propertyName }` );
@@ -73,6 +74,10 @@ export const initConfigureElementTool = ( reg: MCPRegistryEntry ) => {
73
74
  propertyValue,
74
75
  } );
75
76
  } catch ( error ) {
77
+ if ( error instanceof UnsupportedPropertyError ) {
78
+ skippedProps.push( error.propertyName );
79
+ continue;
80
+ }
76
81
  const errorMessage = createUpdateErrorMessage( {
77
82
  propertyName,
78
83
  elementId,
@@ -86,6 +91,11 @@ export const initConfigureElementTool = ( reg: MCPRegistryEntry ) => {
86
91
  await applyStyleFromCss( { elementId, elementType, style } );
87
92
  return {
88
93
  success: true,
94
+ warnings: skippedProps.length
95
+ ? `Skipped unsupported props (not in the "${ elementType }" schema; other changes were applied): ${ skippedProps.join(
96
+ ', '
97
+ ) }.`
98
+ : undefined,
89
99
  };
90
100
  },
91
101
  } );
@@ -8,7 +8,7 @@ import { Schema } from '@elementor/editor-props';
8
8
  import { getStylesSchema, getVariantByMeta } from '@elementor/editor-styles';
9
9
  import { __privateRunCommandSync } from '@elementor/editor-v1-adapters';
10
10
 
11
- import { doUpdateElementProperty } from '../do-update-element-property';
11
+ import { doUpdateElementProperty, UnsupportedPropertyError } from '../do-update-element-property';
12
12
 
13
13
  jest.mock( '@elementor/editor-elements', () => ( {
14
14
  createElementStyle: jest.fn(),
@@ -108,6 +108,32 @@ describe( 'doUpdateElementProperty', () => {
108
108
  expect( __privateRunCommandSync ).not.toHaveBeenCalled();
109
109
  } );
110
110
 
111
+ it( 'throws UnsupportedPropertyError when the property is not in the element schema', () => {
112
+ // Arrange
113
+ const propertyName = 'link';
114
+
115
+ // Act
116
+ let thrown: unknown;
117
+ try {
118
+ doUpdateElementProperty( {
119
+ elementId: ELEMENT_ID,
120
+ elementType: ELEMENT_TYPE,
121
+ propertyName,
122
+ propertyValue: { $$type: 'link', value: {} },
123
+ } );
124
+ } catch ( error ) {
125
+ thrown = error;
126
+ }
127
+
128
+ // Assert
129
+ expect( thrown ).toBeInstanceOf( UnsupportedPropertyError );
130
+ expect( ( thrown as UnsupportedPropertyError ).propertyName ).toBe( propertyName );
131
+ expect( ( thrown as UnsupportedPropertyError ).elementType ).toBe( ELEMENT_TYPE );
132
+ expect( ( thrown as Error ).message ).toContain( 'does not exist on element type' );
133
+ expect( Schema.validatePropValue ).not.toHaveBeenCalled();
134
+ expect( updateElementSettings ).not.toHaveBeenCalled();
135
+ } );
136
+
111
137
  it( 'updates settings when Schema.validatePropValue reports valid PropValue', () => {
112
138
  // Arrange
113
139
  const propertyValue = 'resolved-title';
@@ -21,6 +21,23 @@ const LOCAL_STYLE_META = {
21
21
  breakpoint: 'desktop',
22
22
  state: null,
23
23
  } as const;
24
+
25
+ export class UnsupportedPropertyError extends Error {
26
+ public readonly elementType: string;
27
+ public readonly propertyName: string;
28
+
29
+ constructor( elementType: string, propertyName: string, availableProperties: string[] ) {
30
+ super(
31
+ `Property "${ propertyName }" does not exist on element type "${ elementType }". Available properties are: ${ availableProperties.join(
32
+ ', '
33
+ ) }`
34
+ );
35
+ this.name = 'UnsupportedPropertyError';
36
+ this.elementType = elementType;
37
+ this.propertyName = propertyName;
38
+ }
39
+ }
40
+
24
41
  type CustomCssWriteMode = 'replace' | 'merge-with-stored';
25
42
  type OwnParams = {
26
43
  elementId: string;
@@ -151,12 +168,7 @@ export const doUpdateElementProperty = ( params: OwnParams ) => {
151
168
  throw new Error( `No prop schema found for element type: ${ elementType }` );
152
169
  }
153
170
  if ( ! elementPropSchema[ propertyName ] ) {
154
- const propertyNames = Object.keys( elementPropSchema );
155
- throw new Error(
156
- `Property "${ propertyName }" does not exist on element type "${ elementType }". Available properties are: ${ propertyNames.join(
157
- ', '
158
- ) }`
159
- );
171
+ throw new UnsupportedPropertyError( elementType, propertyName, Object.keys( elementPropSchema ) );
160
172
  }
161
173
  const propKey = elementPropSchema[ propertyName ].key;
162
174
  const value = resolvePropValue( propertyValue, propKey );