@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 +39 -13
- package/dist/index.mjs +39 -13
- package/package.json +20 -20
- package/src/mcp/tools/build-composition/tool.ts +11 -1
- package/src/mcp/tools/configure-element/__tests__/tool.test.ts +130 -0
- package/src/mcp/tools/configure-element/prompt.ts +2 -3
- package/src/mcp/tools/configure-element/schema.ts +6 -0
- package/src/mcp/tools/configure-element/tool.ts +11 -1
- package/src/mcp/utils/__tests__/do-update-element-property.test.ts +27 -1
- package/src/mcp/utils/do-update-element-property.ts +18 -6
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
|
-
|
|
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
|
-
|
|
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-
|
|
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-
|
|
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-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-
|
|
59
|
-
"@elementor/wp-media": "4.3.0-
|
|
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
|
-
|
|
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 );
|