@umbraco-cms/mcp-dev 17.0.0 → 17.0.1-beta.1

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.cjs CHANGED
@@ -7597,7 +7597,7 @@ var UmbracoManagementClient2 = (_class2 = class {
7597
7597
  // package.json
7598
7598
  var package_default = {
7599
7599
  name: "@umbraco-cms/mcp-dev",
7600
- version: "17.0.0",
7600
+ version: "17.0.1-beta.1",
7601
7601
  type: "module",
7602
7602
  description: "A model context protocol (MCP) server for Umbraco CMS",
7603
7603
  main: "index.js",
@@ -7620,6 +7620,8 @@ var package_default = {
7620
7620
  "eval-mcp:create-document-type": "npx mcp-server-tester@1.4.0 evals tests/e2e/create-document-type/create-document-type.yaml --server-config tests/e2e/create-document-type/create-document-type-config.json",
7621
7621
  "eval-mcp:create-document-copy": "npx mcp-server-tester@1.4.0 evals tests/e2e/create-document-copy/create-document-copy.yaml --server-config tests/e2e/create-document-copy/create-document-copy-config.json",
7622
7622
  "eval-mcp:create-document-with-template": "npx mcp-server-tester@1.4.0 evals tests/e2e/create-document-with-template/create-document-with-template.yaml --server-config tests/e2e/create-document-with-template/create-document-with-template-config.json",
7623
+ "eval-mcp:update-document-properties": "npx mcp-server-tester@1.4.0 evals tests/e2e/update-document-properties/update-document-properties.yaml --server-config tests/e2e/update-document-properties/update-document-properties-config.json",
7624
+ "eval-mcp:update-block-property": "npx mcp-server-tester@1.4.0 evals tests/e2e/update-block-property/update-block-property.yaml --server-config tests/e2e/update-block-property/update-block-property-config.json",
7623
7625
  "eval-mcp:all": "npm run eval-mcp:basic && npm run eval-mcp:create-data-type && npm run eval-mcp:create-document-type && npm run eval-mcp:create-blog-post"
7624
7626
  },
7625
7627
  engines: {
@@ -19544,9 +19546,19 @@ var move_to_recycle_bin_default = MoveDocumentToRecycleBinTool;
19544
19546
 
19545
19547
  var UpdateDocumentTool = CreateUmbracoTool(
19546
19548
  "update-document",
19547
- `Updates a document by Id
19548
- Always read the current document value first and only update the required values.
19549
- Don't miss any properties from the original document that you are updating`,
19549
+ `Updates a document by Id. USE AS LAST RESORT ONLY.
19550
+
19551
+ IMPORTANT: Prefer these specialized tools instead:
19552
+ - update-document-properties: For updating individual property values (simpler, safer)
19553
+ - update-block-property: For updating properties within BlockList/BlockGrid/RichText blocks
19554
+
19555
+ Only use this tool when you need to update document-level metadata (template, variants)
19556
+ or when the specialized tools cannot handle your specific use case.
19557
+
19558
+ If you must use this tool:
19559
+ - Always read the current document value first
19560
+ - Only update the required values
19561
+ - Don't miss any properties from the original document`,
19550
19562
  {
19551
19563
  id: putDocumentByIdParams.shape.id,
19552
19564
  data: _zod.z.object(putDocumentByIdBody.shape)
@@ -19567,6 +19579,761 @@ var UpdateDocumentTool = CreateUmbracoTool(
19567
19579
  );
19568
19580
  var update_document_default = UpdateDocumentTool;
19569
19581
 
19582
+ // src/umb-management-api/tools/document/put/update-document-properties.ts
19583
+
19584
+
19585
+ // src/umb-management-api/tools/document/put/helpers/document-type-properties-resolver.ts
19586
+ async function getAllDocumentTypeProperties(documentTypeId) {
19587
+ const client = UmbracoManagementClient2.getClient();
19588
+ const visitedIds = /* @__PURE__ */ new Set();
19589
+ const properties = [];
19590
+ async function resolveDocumentType(docTypeId) {
19591
+ if (visitedIds.has(docTypeId)) {
19592
+ return;
19593
+ }
19594
+ visitedIds.add(docTypeId);
19595
+ const docType = await client.getDocumentTypeById(docTypeId);
19596
+ for (const prop of docType.properties) {
19597
+ if (!properties.some((p) => p.alias === prop.alias)) {
19598
+ properties.push({
19599
+ alias: prop.alias,
19600
+ name: prop.name,
19601
+ dataTypeId: prop.dataType.id,
19602
+ variesByCulture: prop.variesByCulture,
19603
+ variesBySegment: prop.variesBySegment
19604
+ });
19605
+ }
19606
+ }
19607
+ for (const composition of docType.compositions) {
19608
+ await resolveDocumentType(composition.documentType.id);
19609
+ }
19610
+ }
19611
+ await resolveDocumentType(documentTypeId);
19612
+ return properties;
19613
+ }
19614
+ function validateCultureSegment(prop, def) {
19615
+ if (!def.variesByCulture && prop.culture) {
19616
+ return `Property '${prop.alias}' does not vary by culture, but culture '${prop.culture}' was provided`;
19617
+ }
19618
+ if (def.variesByCulture && !prop.culture) {
19619
+ return `Property '${prop.alias}' varies by culture - culture is required`;
19620
+ }
19621
+ if (!def.variesBySegment && prop.segment) {
19622
+ return `Property '${prop.alias}' does not vary by segment, but segment '${prop.segment}' was provided`;
19623
+ }
19624
+ if (def.variesBySegment && !prop.segment) {
19625
+ return `Property '${prop.alias}' varies by segment - segment is required`;
19626
+ }
19627
+ return null;
19628
+ }
19629
+
19630
+ // src/umb-management-api/tools/document/put/helpers/property-value-validator.ts
19631
+ var EDITOR_FORMAT_MAP = {
19632
+ // Editors with allowed values validation
19633
+ "Umbraco.DropDown.Flexible": { valueType: "string[]", allowedValuesKey: "items", validateAllowedValues: true },
19634
+ "Umbraco.RadioButtonList": { valueType: "string", allowedValuesKey: "items", validateAllowedValues: true },
19635
+ "Umbraco.CheckBoxList": { valueType: "string[]", allowedValuesKey: "items", validateAllowedValues: true },
19636
+ "Umbraco.ColorPicker": { valueType: "object", allowedValuesKey: "items", validateAllowedValues: true },
19637
+ // Editors with format-only validation
19638
+ "Umbraco.TextBox": { valueType: "string" },
19639
+ "Umbraco.TextArea": { valueType: "string" },
19640
+ "Umbraco.Integer": { valueType: "number" },
19641
+ "Umbraco.Decimal": { valueType: "number" },
19642
+ "Umbraco.TrueFalse": { valueType: "boolean" },
19643
+ "Umbraco.Tags": { valueType: "string[]" },
19644
+ "Umbraco.MultipleTextstring": { valueType: "string[]" },
19645
+ "Umbraco.DateTime": { valueType: "string" },
19646
+ "Umbraco.EmailAddress": { valueType: "string" },
19647
+ "Umbraco.MarkdownEditor": { valueType: "string" },
19648
+ "Umbraco.ColorPicker.EyeDropper": { valueType: "string" }
19649
+ };
19650
+ function validateValueType(value, expectedType, propertyAlias, editorAlias) {
19651
+ if (value === null || value === void 0) {
19652
+ return null;
19653
+ }
19654
+ switch (expectedType) {
19655
+ case "string":
19656
+ if (typeof value !== "string") {
19657
+ return `Property '${propertyAlias}': Value must be a string for ${editorAlias} editor, got ${typeof value}`;
19658
+ }
19659
+ break;
19660
+ case "string[]":
19661
+ if (!Array.isArray(value)) {
19662
+ return `Property '${propertyAlias}': Value must be an array for ${editorAlias} editor, got ${typeof value}`;
19663
+ }
19664
+ if (!value.every((item) => typeof item === "string")) {
19665
+ return `Property '${propertyAlias}': All array items must be strings for ${editorAlias} editor`;
19666
+ }
19667
+ break;
19668
+ case "number":
19669
+ if (typeof value !== "number") {
19670
+ return `Property '${propertyAlias}': Value must be a number for ${editorAlias} editor, got ${typeof value}`;
19671
+ }
19672
+ break;
19673
+ case "boolean":
19674
+ if (typeof value !== "boolean") {
19675
+ return `Property '${propertyAlias}': Value must be a boolean for ${editorAlias} editor, got ${typeof value}`;
19676
+ }
19677
+ break;
19678
+ case "object":
19679
+ if (typeof value !== "object" || Array.isArray(value)) {
19680
+ return `Property '${propertyAlias}': Value must be an object for ${editorAlias} editor, got ${Array.isArray(value) ? "array" : typeof value}`;
19681
+ }
19682
+ break;
19683
+ }
19684
+ return null;
19685
+ }
19686
+ function extractAllowedValues(dataType, configKey) {
19687
+ const configItem = dataType.values.find((v) => v.alias === configKey);
19688
+ if (!configItem || !configItem.value) {
19689
+ return null;
19690
+ }
19691
+ const value = configItem.value;
19692
+ if (Array.isArray(value)) {
19693
+ if (value.every((item) => typeof item === "string")) {
19694
+ return value;
19695
+ }
19696
+ if (value.every((item) => typeof item === "object" && item !== null && "value" in item)) {
19697
+ return value.map((item) => item.value);
19698
+ }
19699
+ }
19700
+ return null;
19701
+ }
19702
+ function validateAllowedValues(value, allowedValues, propertyAlias, editorAlias, valueType) {
19703
+ const errors = [];
19704
+ if (value === null || value === void 0) {
19705
+ return errors;
19706
+ }
19707
+ if (editorAlias === "Umbraco.ColorPicker" && typeof value === "object" && value !== null && "value" in value) {
19708
+ const colorValue = value.value;
19709
+ if (!allowedValues.includes(colorValue)) {
19710
+ errors.push(`Property '${propertyAlias}': Color value '${colorValue}' is not in allowed values: [${allowedValues.map((v) => `'${v}'`).join(", ")}]`);
19711
+ }
19712
+ return errors;
19713
+ }
19714
+ if (valueType === "string[]" && Array.isArray(value)) {
19715
+ for (const item of value) {
19716
+ if (!allowedValues.includes(item)) {
19717
+ errors.push(`Property '${propertyAlias}': Value '${item}' is not in allowed values: [${allowedValues.map((v) => `'${v}'`).join(", ")}]`);
19718
+ }
19719
+ }
19720
+ return errors;
19721
+ }
19722
+ if (valueType === "string" && typeof value === "string") {
19723
+ if (!allowedValues.includes(value)) {
19724
+ errors.push(`Property '${propertyAlias}': Value '${value}' is not in allowed values: [${allowedValues.map((v) => `'${v}'`).join(", ")}]`);
19725
+ }
19726
+ return errors;
19727
+ }
19728
+ return errors;
19729
+ }
19730
+ function validatePropertyValue(dataType, value, propertyAlias) {
19731
+ const errors = [];
19732
+ const editorAlias = dataType.editorAlias;
19733
+ const formatConfig = EDITOR_FORMAT_MAP[editorAlias];
19734
+ if (!formatConfig) {
19735
+ return { isValid: true, errors: [] };
19736
+ }
19737
+ const typeError = validateValueType(value, formatConfig.valueType, propertyAlias, editorAlias);
19738
+ if (typeError) {
19739
+ errors.push(typeError);
19740
+ return { isValid: false, errors };
19741
+ }
19742
+ if (formatConfig.validateAllowedValues && formatConfig.allowedValuesKey) {
19743
+ const allowedValues = extractAllowedValues(dataType, formatConfig.allowedValuesKey);
19744
+ if (allowedValues && allowedValues.length > 0) {
19745
+ const allowedValueErrors = validateAllowedValues(
19746
+ value,
19747
+ allowedValues,
19748
+ propertyAlias,
19749
+ editorAlias,
19750
+ formatConfig.valueType
19751
+ );
19752
+ errors.push(...allowedValueErrors);
19753
+ }
19754
+ }
19755
+ return {
19756
+ isValid: errors.length === 0,
19757
+ errors
19758
+ };
19759
+ }
19760
+ async function validatePropertiesBeforeSave(properties) {
19761
+ const client = UmbracoManagementClient2.getClient();
19762
+ const errors = [];
19763
+ const dataTypeCache = /* @__PURE__ */ new Map();
19764
+ async function getCachedDataType(dataTypeId) {
19765
+ if (!dataTypeCache.has(dataTypeId)) {
19766
+ try {
19767
+ const dataType = await client.getDataTypeById(dataTypeId);
19768
+ dataTypeCache.set(dataTypeId, dataType);
19769
+ } catch (error) {
19770
+ console.error(`Failed to fetch Data Type ${dataTypeId}:`, error);
19771
+ return null;
19772
+ }
19773
+ }
19774
+ return _nullishCoalesce(dataTypeCache.get(dataTypeId), () => ( null));
19775
+ }
19776
+ for (const prop of properties) {
19777
+ if (!prop.dataTypeId) {
19778
+ continue;
19779
+ }
19780
+ const dataType = await getCachedDataType(prop.dataTypeId);
19781
+ if (!dataType) {
19782
+ continue;
19783
+ }
19784
+ const validation = validatePropertyValue(dataType, prop.value, prop.alias);
19785
+ if (!validation.isValid) {
19786
+ errors.push(...validation.errors);
19787
+ }
19788
+ }
19789
+ return {
19790
+ isValid: errors.length === 0,
19791
+ errors
19792
+ };
19793
+ }
19794
+
19795
+ // src/umb-management-api/tools/document/put/helpers/property-matching.ts
19796
+ function matchesProperty(value, alias, culture, segment) {
19797
+ return value.alias === alias && (_nullishCoalesce(value.culture, () => ( null))) === (_nullishCoalesce(culture, () => ( null))) && (_nullishCoalesce(value.segment, () => ( null))) === (_nullishCoalesce(segment, () => ( null)));
19798
+ }
19799
+ function getPropertyKey(alias, culture, segment) {
19800
+ let key = alias;
19801
+ if (culture) key += `[${culture}]`;
19802
+ if (segment) key += `[${segment}]`;
19803
+ return key;
19804
+ }
19805
+
19806
+ // src/umb-management-api/tools/document/put/update-document-properties.ts
19807
+ var propertySchema2 = _zod.z.object({
19808
+ alias: _zod.z.string().min(1).describe("The property alias to update or add"),
19809
+ value: _zod.z.any().nullish().describe("The new value for the property"),
19810
+ culture: _zod.z.string().nullish().describe("Optional culture code for variant properties (e.g., 'en-US')"),
19811
+ segment: _zod.z.string().nullish().describe("Optional segment identifier for variant properties")
19812
+ });
19813
+ var updateDocumentPropertiesSchema = {
19814
+ id: _zod.z.string().uuid().describe("The unique identifier of the document to update"),
19815
+ properties: _zod.z.array(propertySchema2).min(1).describe("Array of properties to update or add - at least one property is required")
19816
+ };
19817
+ var UpdateDocumentPropertiesTool = CreateUmbracoTool(
19818
+ "update-document-properties",
19819
+ `Updates or adds property values on a document without requiring the full document JSON payload.
19820
+
19821
+ This tool simplifies property updates by handling the read-modify-write cycle internally.
19822
+ You can update existing properties, add new properties, or do both in a single call.
19823
+
19824
+ Key features:
19825
+ - Update existing properties or add new ones
19826
+ - Property must exist on the document type (including compositions)
19827
+ - Full i18n support with culture and segment parameters
19828
+ - Automatic validation of property aliases against document type
19829
+ - Culture/segment requirements validated against property variance flags
19830
+ - Returns detailed error messages with available properties
19831
+
19832
+ Example usage:
19833
+ - Update a single property: { id: "...", properties: [{ alias: "title", value: "New Title" }] }
19834
+ - Add a new property: { id: "...", properties: [{ alias: "author", value: "John Doe" }] }
19835
+ - Update with culture: { id: "...", properties: [{ alias: "title", value: "Nuevo T\xEDtulo", culture: "es-ES" }] }
19836
+ - Mix update and add: { id: "...", properties: [{ alias: "title", value: "New" }, { alias: "newProp", value: "Value" }] }`,
19837
+ updateDocumentPropertiesSchema,
19838
+ async (model) => {
19839
+ const client = UmbracoManagementClient2.getClient();
19840
+ const currentDocument = await client.getDocumentById(model.id);
19841
+ const invalidAliases = [];
19842
+ const varianceErrors = [];
19843
+ const propertiesToUpdate = [];
19844
+ const propertiesToAdd = [];
19845
+ let documentTypeProperties = null;
19846
+ const getDocumentTypeProperties = async () => {
19847
+ if (documentTypeProperties === null) {
19848
+ try {
19849
+ documentTypeProperties = await getAllDocumentTypeProperties(currentDocument.documentType.id);
19850
+ } catch (error) {
19851
+ console.error("Failed to fetch document type properties:", error);
19852
+ documentTypeProperties = [];
19853
+ }
19854
+ }
19855
+ return documentTypeProperties;
19856
+ };
19857
+ for (const prop of model.properties) {
19858
+ const existsOnDocument = currentDocument.values.some(
19859
+ (v) => matchesProperty(v, prop.alias, prop.culture, prop.segment)
19860
+ );
19861
+ if (existsOnDocument) {
19862
+ propertiesToUpdate.push({
19863
+ alias: prop.alias,
19864
+ value: prop.value,
19865
+ culture: prop.culture,
19866
+ segment: prop.segment
19867
+ });
19868
+ } else {
19869
+ const docTypeProperties = await getDocumentTypeProperties();
19870
+ const propertyDef = docTypeProperties.find((p) => p.alias === prop.alias);
19871
+ if (propertyDef) {
19872
+ const validationError = validateCultureSegment(prop, propertyDef);
19873
+ if (validationError) {
19874
+ varianceErrors.push(validationError);
19875
+ } else {
19876
+ propertiesToAdd.push({
19877
+ alias: prop.alias,
19878
+ value: prop.value,
19879
+ culture: prop.culture,
19880
+ segment: prop.segment
19881
+ });
19882
+ }
19883
+ } else if (docTypeProperties.length > 0) {
19884
+ invalidAliases.push(getPropertyKey(prop.alias, prop.culture, prop.segment));
19885
+ } else {
19886
+ invalidAliases.push(getPropertyKey(prop.alias, prop.culture, prop.segment));
19887
+ }
19888
+ }
19889
+ }
19890
+ if (varianceErrors.length > 0) {
19891
+ return {
19892
+ content: [{
19893
+ type: "text",
19894
+ text: JSON.stringify({
19895
+ success: false,
19896
+ error: "Culture/segment validation failed",
19897
+ message: varianceErrors.join("; "),
19898
+ errors: varianceErrors,
19899
+ availableProperties: (_nullishCoalesce(documentTypeProperties, () => ( []))).map((p) => ({
19900
+ alias: p.alias,
19901
+ name: p.name,
19902
+ variesByCulture: p.variesByCulture,
19903
+ variesBySegment: p.variesBySegment
19904
+ }))
19905
+ }, null, 2)
19906
+ }]
19907
+ };
19908
+ }
19909
+ if (invalidAliases.length > 0) {
19910
+ const docTypeProps = _nullishCoalesce(documentTypeProperties, () => ( []));
19911
+ const availableProperties = docTypeProps.length > 0 ? docTypeProps.map((p) => ({
19912
+ alias: p.alias,
19913
+ name: p.name,
19914
+ variesByCulture: p.variesByCulture,
19915
+ variesBySegment: p.variesBySegment
19916
+ })) : currentDocument.values.map((v) => ({
19917
+ alias: v.alias,
19918
+ culture: _nullishCoalesce(v.culture, () => ( null)),
19919
+ segment: _nullishCoalesce(v.segment, () => ( null)),
19920
+ editorAlias: v.editorAlias
19921
+ }));
19922
+ return {
19923
+ content: [{
19924
+ type: "text",
19925
+ text: JSON.stringify({
19926
+ success: false,
19927
+ error: "Invalid property aliases",
19928
+ message: `The following properties do not exist on this document type: ${invalidAliases.join(", ")}`,
19929
+ invalidAliases,
19930
+ availableProperties
19931
+ }, null, 2)
19932
+ }]
19933
+ };
19934
+ }
19935
+ const allPropertiesToValidate = [...propertiesToUpdate, ...propertiesToAdd];
19936
+ if (allPropertiesToValidate.length > 0) {
19937
+ const docTypeProps = await getDocumentTypeProperties();
19938
+ const propsToValidate = allPropertiesToValidate.map((p) => {
19939
+ const def = docTypeProps.find((d) => d.alias === p.alias);
19940
+ return { alias: p.alias, value: p.value, dataTypeId: _nullishCoalesce(_optionalChain([def, 'optionalAccess', _50 => _50.dataTypeId]), () => ( "")) };
19941
+ }).filter((p) => p.dataTypeId);
19942
+ if (propsToValidate.length > 0) {
19943
+ const valueValidation = await validatePropertiesBeforeSave(propsToValidate);
19944
+ if (!valueValidation.isValid) {
19945
+ return {
19946
+ content: [{
19947
+ type: "text",
19948
+ text: JSON.stringify({
19949
+ success: false,
19950
+ error: "Property value validation failed",
19951
+ errors: valueValidation.errors
19952
+ }, null, 2)
19953
+ }]
19954
+ };
19955
+ }
19956
+ }
19957
+ }
19958
+ const updatedValues = currentDocument.values.map((existingValue) => {
19959
+ const updateProp = propertiesToUpdate.find(
19960
+ (p) => matchesProperty(existingValue, p.alias, p.culture, p.segment)
19961
+ );
19962
+ if (updateProp) {
19963
+ return {
19964
+ alias: existingValue.alias,
19965
+ culture: existingValue.culture,
19966
+ segment: existingValue.segment,
19967
+ value: updateProp.value
19968
+ };
19969
+ }
19970
+ return {
19971
+ alias: existingValue.alias,
19972
+ culture: existingValue.culture,
19973
+ segment: existingValue.segment,
19974
+ value: existingValue.value
19975
+ };
19976
+ });
19977
+ for (const newProp of propertiesToAdd) {
19978
+ updatedValues.push({
19979
+ alias: newProp.alias,
19980
+ culture: _nullishCoalesce(newProp.culture, () => ( null)),
19981
+ segment: _nullishCoalesce(newProp.segment, () => ( null)),
19982
+ value: newProp.value
19983
+ });
19984
+ }
19985
+ const variants = currentDocument.variants.map((v) => ({
19986
+ culture: v.culture,
19987
+ segment: v.segment,
19988
+ name: v.name
19989
+ }));
19990
+ const updatePayload = {
19991
+ values: updatedValues,
19992
+ variants,
19993
+ template: currentDocument.template
19994
+ };
19995
+ await client.putDocumentById(model.id, updatePayload);
19996
+ const updatedDocument = await client.getDocumentById(model.id);
19997
+ const updatedPropertyKeys = propertiesToUpdate.map(
19998
+ (p) => getPropertyKey(p.alias, p.culture, p.segment)
19999
+ );
20000
+ const addedPropertyKeys = propertiesToAdd.map(
20001
+ (p) => getPropertyKey(p.alias, p.culture, p.segment)
20002
+ );
20003
+ const totalCount = propertiesToUpdate.length + propertiesToAdd.length;
20004
+ let message = `Successfully processed ${totalCount} property value(s)`;
20005
+ if (propertiesToUpdate.length > 0 && propertiesToAdd.length > 0) {
20006
+ message = `Successfully updated ${propertiesToUpdate.length} and added ${propertiesToAdd.length} property value(s)`;
20007
+ } else if (propertiesToAdd.length > 0) {
20008
+ message = `Successfully added ${propertiesToAdd.length} property value(s)`;
20009
+ } else {
20010
+ message = `Successfully updated ${propertiesToUpdate.length} property value(s)`;
20011
+ }
20012
+ return {
20013
+ content: [{
20014
+ type: "text",
20015
+ text: JSON.stringify({
20016
+ success: true,
20017
+ message,
20018
+ updatedProperties: updatedPropertyKeys,
20019
+ addedProperties: addedPropertyKeys,
20020
+ document: updatedDocument
20021
+ }, null, 2)
20022
+ }]
20023
+ };
20024
+ },
20025
+ (user) => user.fallbackPermissions.includes(UmbracoDocumentPermissions.Update)
20026
+ );
20027
+ var update_document_properties_default = UpdateDocumentPropertiesTool;
20028
+
20029
+ // src/umb-management-api/tools/document/put/update-block-property.ts
20030
+
20031
+
20032
+ // src/umb-management-api/tools/document/put/helpers/block-discovery.ts
20033
+ function isRichTextValue(value) {
20034
+ if (!value || typeof value !== "object") {
20035
+ return false;
20036
+ }
20037
+ if (!("markup" in value)) {
20038
+ return false;
20039
+ }
20040
+ if (!value.blocks || typeof value.blocks !== "object") {
20041
+ return false;
20042
+ }
20043
+ return Array.isArray(value.blocks.contentData) && Array.isArray(value.blocks.settingsData);
20044
+ }
20045
+ function isBlockStructure(value) {
20046
+ if (!value || typeof value !== "object") {
20047
+ return false;
20048
+ }
20049
+ return Array.isArray(value.contentData) && Array.isArray(value.settingsData);
20050
+ }
20051
+ function discoverAllBlockArrays(value, path4 = "root") {
20052
+ const results = [];
20053
+ if (!value || typeof value !== "object") {
20054
+ return results;
20055
+ }
20056
+ if (isRichTextValue(value)) {
20057
+ const nestedResults = discoverAllBlockArrays(value.blocks, `${path4}.blocks`);
20058
+ results.push(...nestedResults);
20059
+ return results;
20060
+ }
20061
+ if (isBlockStructure(value)) {
20062
+ results.push({
20063
+ contentData: value.contentData,
20064
+ settingsData: value.settingsData,
20065
+ path: path4
20066
+ });
20067
+ value.contentData.forEach((block, index) => {
20068
+ if (block.values && Array.isArray(block.values)) {
20069
+ block.values.forEach((prop, propIndex) => {
20070
+ const propPath = `${path4}.contentData[${index}].values[${propIndex}](${prop.alias})`;
20071
+ if (isRichTextValue(prop.value)) {
20072
+ const nestedResults = discoverAllBlockArrays(prop.value.blocks, `${propPath}.blocks`);
20073
+ results.push(...nestedResults);
20074
+ } else if (isBlockStructure(prop.value)) {
20075
+ const nestedResults = discoverAllBlockArrays(prop.value, propPath);
20076
+ results.push(...nestedResults);
20077
+ }
20078
+ });
20079
+ }
20080
+ });
20081
+ value.settingsData.forEach((block, index) => {
20082
+ if (block.values && Array.isArray(block.values)) {
20083
+ block.values.forEach((prop, propIndex) => {
20084
+ const propPath = `${path4}.settingsData[${index}].values[${propIndex}](${prop.alias})`;
20085
+ if (isRichTextValue(prop.value)) {
20086
+ const nestedResults = discoverAllBlockArrays(prop.value.blocks, `${propPath}.blocks`);
20087
+ results.push(...nestedResults);
20088
+ } else if (isBlockStructure(prop.value)) {
20089
+ const nestedResults = discoverAllBlockArrays(prop.value, propPath);
20090
+ results.push(...nestedResults);
20091
+ }
20092
+ });
20093
+ }
20094
+ });
20095
+ }
20096
+ return results;
20097
+ }
20098
+ function findBlockByKey(discoveredArrays, contentKey, blockType) {
20099
+ for (const discovered of discoveredArrays) {
20100
+ const array = blockType === "content" ? discovered.contentData : discovered.settingsData;
20101
+ const block = array.find((b) => b.key === contentKey);
20102
+ if (block) {
20103
+ return { block, arrayRef: array, path: discovered.path };
20104
+ }
20105
+ }
20106
+ return null;
20107
+ }
20108
+
20109
+ // src/umb-management-api/tools/document/put/update-block-property.ts
20110
+ var blockPropertyUpdateSchema = _zod.z.object({
20111
+ alias: _zod.z.string().min(1).describe("The property alias within the block to update"),
20112
+ value: _zod.z.any().nullish().describe("The new value for the property"),
20113
+ culture: _zod.z.string().nullish().describe("Optional culture code for variant block properties"),
20114
+ segment: _zod.z.string().nullish().describe("Optional segment identifier for variant block properties")
20115
+ });
20116
+ var blockUpdateSchema = _zod.z.object({
20117
+ contentKey: _zod.z.string().uuid().describe("The unique key (UUID) identifying the block"),
20118
+ blockType: _zod.z.enum(["content", "settings"]).describe("'content' for contentData, 'settings' for settingsData"),
20119
+ properties: _zod.z.array(blockPropertyUpdateSchema).min(1).describe("Properties to update within this block")
20120
+ });
20121
+ var updateBlockPropertySchema = {
20122
+ documentId: _zod.z.string().uuid().describe("The document containing the block"),
20123
+ propertyAlias: _zod.z.string().min(1).describe("Document property alias containing BlockList/BlockGrid"),
20124
+ culture: _zod.z.string().nullish().describe("Optional culture for variant document properties"),
20125
+ segment: _zod.z.string().nullish().describe("Optional segment for variant document properties"),
20126
+ updates: _zod.z.array(blockUpdateSchema).min(1).describe("Array of block updates")
20127
+ };
20128
+ var UpdateBlockPropertyTool = CreateUmbracoTool(
20129
+ "update-block-property",
20130
+ `Updates or adds property values within BlockList, BlockGrid, or RichText block content.
20131
+
20132
+ This tool enables targeted updates to individual block properties without sending the entire JSON payload.
20133
+ You can update existing properties, add new properties, or do both in a single call.
20134
+ It automatically handles deep traversal of nested block structures (e.g., RichText blocks containing nested blocks).
20135
+
20136
+ Key features:
20137
+ - Update existing properties or add new ones to blocks
20138
+ - Property must exist on the Element Type (including compositions)
20139
+ - Support for both content and settings blocks
20140
+ - Batch updates to multiple blocks in a single call
20141
+ - Deep traversal of nested block structures
20142
+ - Full i18n support with culture and segment parameters
20143
+ - Culture/segment requirements validated against property variance flags
20144
+
20145
+ Example usage:
20146
+ - Update a block property: { documentId: "...", propertyAlias: "mainContent", updates: [{ contentKey: "block-uuid", blockType: "content", properties: [{ alias: "title", value: "New Title" }] }] }
20147
+ - Add a new property to block: { documentId: "...", propertyAlias: "mainContent", updates: [{ contentKey: "block-uuid", blockType: "content", properties: [{ alias: "newProp", value: "Value" }] }] }
20148
+ - Update with culture: { documentId: "...", propertyAlias: "mainContent", culture: "es-ES", updates: [...] }
20149
+ - Batch update multiple blocks: { documentId: "...", propertyAlias: "mainContent", updates: [{ contentKey: "uuid1", ... }, { contentKey: "uuid2", ... }] }`,
20150
+ updateBlockPropertySchema,
20151
+ async (model) => {
20152
+ const client = UmbracoManagementClient2.getClient();
20153
+ const currentDocument = await client.getDocumentById(model.documentId);
20154
+ const documentProperty = currentDocument.values.find(
20155
+ (v) => matchesProperty(v, model.propertyAlias, model.culture, model.segment)
20156
+ );
20157
+ if (!documentProperty) {
20158
+ const availableAliases = currentDocument.values.map((v) => ({
20159
+ alias: v.alias,
20160
+ culture: _nullishCoalesce(v.culture, () => ( null)),
20161
+ segment: _nullishCoalesce(v.segment, () => ( null)),
20162
+ editorAlias: v.editorAlias
20163
+ }));
20164
+ return {
20165
+ content: [{
20166
+ type: "text",
20167
+ text: JSON.stringify({
20168
+ success: false,
20169
+ error: "Property not found",
20170
+ message: `Property '${getPropertyKey(model.propertyAlias, model.culture, model.segment)}' does not exist on this document`,
20171
+ availableProperties: availableAliases
20172
+ }, null, 2)
20173
+ }]
20174
+ };
20175
+ }
20176
+ const discoveredArrays = discoverAllBlockArrays(documentProperty.value, `property(${model.propertyAlias})`);
20177
+ if (discoveredArrays.length === 0) {
20178
+ return {
20179
+ content: [{
20180
+ type: "text",
20181
+ text: JSON.stringify({
20182
+ success: false,
20183
+ error: "No block structure found",
20184
+ message: `Property '${model.propertyAlias}' does not contain a BlockList, BlockGrid, or RichText block structure`,
20185
+ propertyValue: documentProperty.value
20186
+ }, null, 2)
20187
+ }]
20188
+ };
20189
+ }
20190
+ const results = [];
20191
+ const notFoundBlocks = [];
20192
+ const elementTypePropertiesCache = /* @__PURE__ */ new Map();
20193
+ const getElementTypeProperties = async (contentTypeKey) => {
20194
+ if (!elementTypePropertiesCache.has(contentTypeKey)) {
20195
+ try {
20196
+ const properties = await getAllDocumentTypeProperties(contentTypeKey);
20197
+ elementTypePropertiesCache.set(contentTypeKey, properties);
20198
+ } catch (error) {
20199
+ console.error(`Failed to fetch Element Type properties for ${contentTypeKey}:`, error);
20200
+ elementTypePropertiesCache.set(contentTypeKey, []);
20201
+ }
20202
+ }
20203
+ return elementTypePropertiesCache.get(contentTypeKey);
20204
+ };
20205
+ for (const update of model.updates) {
20206
+ const foundBlock = findBlockByKey(discoveredArrays, update.contentKey, update.blockType);
20207
+ if (!foundBlock) {
20208
+ notFoundBlocks.push({ contentKey: update.contentKey, blockType: update.blockType });
20209
+ results.push({
20210
+ success: false,
20211
+ contentKey: update.contentKey,
20212
+ message: `Block with contentKey '${update.contentKey}' not found in ${update.blockType}Data`
20213
+ });
20214
+ continue;
20215
+ }
20216
+ const warnings = [];
20217
+ const errors = [];
20218
+ let updatedCount = 0;
20219
+ let addedCount = 0;
20220
+ const elementTypeProperties = await getElementTypeProperties(foundBlock.block.contentTypeKey);
20221
+ if (elementTypeProperties.length > 0) {
20222
+ const propsToValidate = update.properties.map((p) => {
20223
+ const def = elementTypeProperties.find((d) => d.alias === p.alias);
20224
+ return { alias: p.alias, value: p.value, dataTypeId: _nullishCoalesce(_optionalChain([def, 'optionalAccess', _51 => _51.dataTypeId]), () => ( "")) };
20225
+ }).filter((p) => p.dataTypeId);
20226
+ if (propsToValidate.length > 0) {
20227
+ const valueValidation = await validatePropertiesBeforeSave(propsToValidate);
20228
+ if (!valueValidation.isValid) {
20229
+ errors.push(...valueValidation.errors);
20230
+ }
20231
+ }
20232
+ }
20233
+ for (const propUpdate of update.properties) {
20234
+ const blockProperty = foundBlock.block.values.find(
20235
+ (v) => matchesProperty(v, propUpdate.alias, propUpdate.culture, propUpdate.segment)
20236
+ );
20237
+ if (blockProperty) {
20238
+ blockProperty.value = propUpdate.value;
20239
+ updatedCount++;
20240
+ } else {
20241
+ const propertyDef = elementTypeProperties.find((p) => p.alias === propUpdate.alias);
20242
+ if (propertyDef) {
20243
+ const validationError = validateCultureSegment(propUpdate, propertyDef);
20244
+ if (validationError) {
20245
+ errors.push(validationError);
20246
+ } else {
20247
+ foundBlock.block.values.push({
20248
+ alias: propUpdate.alias,
20249
+ culture: _nullishCoalesce(propUpdate.culture, () => ( null)),
20250
+ segment: _nullishCoalesce(propUpdate.segment, () => ( null)),
20251
+ value: propUpdate.value
20252
+ });
20253
+ addedCount++;
20254
+ }
20255
+ } else if (elementTypeProperties.length > 0) {
20256
+ errors.push(
20257
+ `Property '${getPropertyKey(propUpdate.alias, propUpdate.culture, propUpdate.segment)}' does not exist on Element Type`
20258
+ );
20259
+ } else {
20260
+ warnings.push(
20261
+ `Property '${getPropertyKey(propUpdate.alias, propUpdate.culture, propUpdate.segment)}' not found in block (could not validate against Element Type)`
20262
+ );
20263
+ }
20264
+ }
20265
+ }
20266
+ const totalProcessed = updatedCount + addedCount;
20267
+ let message;
20268
+ if (updatedCount > 0 && addedCount > 0) {
20269
+ message = `Updated ${updatedCount} and added ${addedCount} properties in block at ${foundBlock.path}`;
20270
+ } else if (addedCount > 0) {
20271
+ message = `Added ${addedCount} properties in block at ${foundBlock.path}`;
20272
+ } else if (updatedCount > 0) {
20273
+ message = `Updated ${updatedCount} properties in block at ${foundBlock.path}`;
20274
+ } else {
20275
+ message = `No properties were processed in block at ${foundBlock.path}`;
20276
+ }
20277
+ results.push({
20278
+ success: totalProcessed > 0 || errors.length === 0,
20279
+ contentKey: update.contentKey,
20280
+ message,
20281
+ updatedCount: updatedCount > 0 ? updatedCount : void 0,
20282
+ addedCount: addedCount > 0 ? addedCount : void 0,
20283
+ warnings: warnings.length > 0 ? warnings : void 0,
20284
+ errors: errors.length > 0 ? errors : void 0
20285
+ });
20286
+ }
20287
+ if (notFoundBlocks.length === model.updates.length) {
20288
+ const allBlocks = discoveredArrays.flatMap((d) => [
20289
+ ...d.contentData.map((b) => ({ key: b.key, type: "content", path: d.path })),
20290
+ ...d.settingsData.map((b) => ({ key: b.key, type: "settings", path: d.path }))
20291
+ ]);
20292
+ return {
20293
+ content: [{
20294
+ type: "text",
20295
+ text: JSON.stringify({
20296
+ success: false,
20297
+ error: "Blocks not found",
20298
+ message: "None of the specified blocks were found in the document",
20299
+ notFoundBlocks,
20300
+ availableBlocks: allBlocks
20301
+ }, null, 2)
20302
+ }]
20303
+ };
20304
+ }
20305
+ const updatedValues = currentDocument.values.map((existingValue) => ({
20306
+ alias: existingValue.alias,
20307
+ culture: existingValue.culture,
20308
+ segment: existingValue.segment,
20309
+ value: existingValue.value
20310
+ }));
20311
+ const variants = currentDocument.variants.map((v) => ({
20312
+ culture: v.culture,
20313
+ segment: v.segment,
20314
+ name: v.name
20315
+ }));
20316
+ const updatePayload = {
20317
+ values: updatedValues,
20318
+ variants,
20319
+ template: currentDocument.template
20320
+ };
20321
+ await client.putDocumentById(model.documentId, updatePayload);
20322
+ return {
20323
+ content: [{
20324
+ type: "text",
20325
+ text: JSON.stringify({
20326
+ success: true,
20327
+ message: `Successfully processed ${model.updates.length} block update(s)`,
20328
+ results
20329
+ }, null, 2)
20330
+ }]
20331
+ };
20332
+ },
20333
+ (user) => user.fallbackPermissions.includes(UmbracoDocumentPermissions.Update)
20334
+ );
20335
+ var update_block_property_default = UpdateBlockPropertyTool;
20336
+
19570
20337
  // src/umb-management-api/tools/document/items/get/get-root.ts
19571
20338
  var GetDocumentRootTool = CreateUmbracoTool(
19572
20339
  "get-document-root",
@@ -19740,6 +20507,8 @@ var DocumentCollection = {
19740
20507
  tools.push(sort_document_default());
19741
20508
  tools.push(unpublish_document_default());
19742
20509
  tools.push(update_document_default());
20510
+ tools.push(update_document_properties_default());
20511
+ tools.push(update_block_property_default());
19743
20512
  tools.push(put_document_domains_default());
19744
20513
  tools.push(put_document_notifications_default());
19745
20514
  tools.push(put_document_public_access_default());
@@ -20165,7 +20934,7 @@ function getExtensionFromMimeType(mimeType) {
20165
20934
  return extension ? `.${extension}` : void 0;
20166
20935
  }
20167
20936
  function validateMediaTypeForSvg(filePath, fileUrl, fileName, mediaTypeName) {
20168
- const isSvg = _optionalChain([filePath, 'optionalAccess', _50 => _50.toLowerCase, 'call', _51 => _51(), 'access', _52 => _52.endsWith, 'call', _53 => _53(".svg")]) || _optionalChain([fileUrl, 'optionalAccess', _54 => _54.toLowerCase, 'call', _55 => _55(), 'access', _56 => _56.endsWith, 'call', _57 => _57(".svg")]) || fileName.toLowerCase().endsWith(".svg");
20937
+ const isSvg = _optionalChain([filePath, 'optionalAccess', _52 => _52.toLowerCase, 'call', _53 => _53(), 'access', _54 => _54.endsWith, 'call', _55 => _55(".svg")]) || _optionalChain([fileUrl, 'optionalAccess', _56 => _56.toLowerCase, 'call', _57 => _57(), 'access', _58 => _58.endsWith, 'call', _59 => _59(".svg")]) || fileName.toLowerCase().endsWith(".svg");
20169
20938
  if (isSvg && mediaTypeName === MEDIA_TYPE_IMAGE) {
20170
20939
  console.warn(`SVG detected - using ${MEDIA_TYPE_VECTOR_GRAPHICS} media type instead of ${MEDIA_TYPE_IMAGE}`);
20171
20940
  return MEDIA_TYPE_VECTOR_GRAPHICS;
@@ -20322,8 +21091,8 @@ async function uploadMediaFile(client, params) {
20322
21091
  });
20323
21092
  } catch (error) {
20324
21093
  const err = error;
20325
- const errorData = _optionalChain([err, 'access', _58 => _58.response, 'optionalAccess', _59 => _59.data]) ? typeof err.response.data === "string" ? err.response.data : JSON.stringify(err.response.data) : err.message;
20326
- throw new Error(`Failed to upload temporary file: ${_optionalChain([err, 'access', _60 => _60.response, 'optionalAccess', _61 => _61.status]) || "Unknown error"} - ${errorData}`);
21094
+ const errorData = _optionalChain([err, 'access', _60 => _60.response, 'optionalAccess', _61 => _61.data]) ? typeof err.response.data === "string" ? err.response.data : JSON.stringify(err.response.data) : err.message;
21095
+ throw new Error(`Failed to upload temporary file: ${_optionalChain([err, 'access', _62 => _62.response, 'optionalAccess', _63 => _63.status]) || "Unknown error"} - ${errorData}`);
20327
21096
  }
20328
21097
  const valueStructure = buildValueStructure(validatedMediaTypeName, params.temporaryFileId);
20329
21098
  try {
@@ -20342,7 +21111,7 @@ async function uploadMediaFile(client, params) {
20342
21111
  });
20343
21112
  } catch (error) {
20344
21113
  const err = error;
20345
- throw new Error(`Failed to create media item: ${_optionalChain([err, 'access', _62 => _62.response, 'optionalAccess', _63 => _63.status]) || "Unknown error"} - ${JSON.stringify(_optionalChain([err, 'access', _64 => _64.response, 'optionalAccess', _65 => _65.data])) || err.message}`);
21114
+ throw new Error(`Failed to create media item: ${_optionalChain([err, 'access', _64 => _64.response, 'optionalAccess', _65 => _65.status]) || "Unknown error"} - ${JSON.stringify(_optionalChain([err, 'access', _66 => _66.response, 'optionalAccess', _67 => _67.data])) || err.message}`);
20346
21115
  }
20347
21116
  return params.name;
20348
21117
  } finally {
@@ -21547,7 +22316,7 @@ var update_folder_default4 = UpdateMediaTypeFolderTool;
21547
22316
 
21548
22317
  // src/umb-management-api/tools/media-type/post/create-media-type.ts
21549
22318
 
21550
- var propertySchema2 = postMediaTypeBody.shape.properties;
22319
+ var propertySchema3 = postMediaTypeBody.shape.properties;
21551
22320
  var containerSchema = postMediaTypeBody.shape.containers;
21552
22321
  var allowedMediaTypeSchema = postMediaTypeBody.shape.allowedMediaTypes;
21553
22322
  var compositionSchema = postMediaTypeBody.shape.compositions;
@@ -21561,7 +22330,7 @@ var createMediaTypeSchema = _zod.z.object({
21561
22330
  variesByCulture: _zod.z.boolean(),
21562
22331
  variesBySegment: _zod.z.boolean(),
21563
22332
  isElement: _zod.z.boolean(),
21564
- properties: propertySchema2,
22333
+ properties: propertySchema3,
21565
22334
  containers: containerSchema,
21566
22335
  id: _zod.z.string().uuid().nullish(),
21567
22336
  parentId: _zod.z.string().uuid().optional(),
@@ -26176,8 +26945,8 @@ var mapTools = (server, user, tools, config) => {
26176
26945
  return tools.forEach((tool) => {
26177
26946
  const userHasPermission = tool.enabled === void 0 || tool.enabled(user);
26178
26947
  if (!userHasPermission) return;
26179
- if (_optionalChain([config, 'access', _66 => _66.disabledTools, 'optionalAccess', _67 => _67.includes, 'call', _68 => _68(tool.name)])) return;
26180
- if (_optionalChain([config, 'access', _69 => _69.enabledTools, 'optionalAccess', _70 => _70.length]) && !config.enabledTools.includes(tool.name)) return;
26948
+ if (_optionalChain([config, 'access', _68 => _68.disabledTools, 'optionalAccess', _69 => _69.includes, 'call', _70 => _70(tool.name)])) return;
26949
+ if (_optionalChain([config, 'access', _71 => _71.enabledTools, 'optionalAccess', _72 => _72.length]) && !config.enabledTools.includes(tool.name)) return;
26181
26950
  server.tool(tool.name, tool.description, tool.schema, tool.handler);
26182
26951
  });
26183
26952
  };
@@ -26198,7 +26967,7 @@ function resolveDependencies(requestedNames, collections) {
26198
26967
  const collectionMap = new Map(collections.map((c) => [c.metadata.name, c]));
26199
26968
  function addDependencies(collectionName) {
26200
26969
  const collection = collectionMap.get(collectionName);
26201
- if (_optionalChain([collection, 'optionalAccess', _71 => _71.metadata, 'access', _72 => _72.dependencies])) {
26970
+ if (_optionalChain([collection, 'optionalAccess', _73 => _73.metadata, 'access', _74 => _74.dependencies])) {
26202
26971
  collection.metadata.dependencies.forEach((dep) => {
26203
26972
  if (!result.has(dep)) {
26204
26973
  result.add(dep);