@kontent-ai/mcp-server 0.21.9 → 0.21.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +30 -1
  2. package/build/bin.js +7 -0
  3. package/build/schemas/contentTypeSchemas.js +24 -61
  4. package/build/schemas/filterVariantSchemas.js +2 -0
  5. package/build/schemas/listSchemas.js +26 -0
  6. package/build/schemas/patchSchemas/contentTypePatchSchemas.js +12 -64
  7. package/build/schemas/referenceObjectSchema.js +3 -5
  8. package/build/schemas/taxonomySchemas.js +4 -9
  9. package/build/test/utils/responseHelper.spec.js +450 -0
  10. package/build/tools/add-content-item-mapi.js +6 -13
  11. package/build/tools/add-content-type-mapi.js +6 -11
  12. package/build/tools/add-content-type-snippet-mapi.js +5 -8
  13. package/build/tools/add-taxonomy-group-mapi.js +1 -1
  14. package/build/tools/change-variant-workflow-step-mapi.js +5 -14
  15. package/build/tools/context/initial-context.js +40 -1
  16. package/build/tools/context/patch-operations-guide.js +58 -0
  17. package/build/tools/create-variant-version-mapi.js +3 -6
  18. package/build/tools/delete-content-item-mapi.js +2 -2
  19. package/build/tools/delete-content-type-mapi.js +2 -2
  20. package/build/tools/delete-language-variant-mapi.js +3 -5
  21. package/build/tools/filter-variants-mapi.js +13 -20
  22. package/build/tools/get-asset-mapi.js +2 -2
  23. package/build/tools/get-initial-context.js +2 -1
  24. package/build/tools/get-item-mapi.js +2 -2
  25. package/build/tools/get-taxonomy-group-mapi.js +2 -2
  26. package/build/tools/get-type-mapi.js +2 -2
  27. package/build/tools/get-type-snippet-mapi.js +2 -2
  28. package/build/tools/get-variant-mapi.js +5 -7
  29. package/build/tools/list-assets-mapi.js +12 -4
  30. package/build/tools/list-content-type-snippets-mapi.js +12 -4
  31. package/build/tools/list-content-types-mapi.js +12 -4
  32. package/build/tools/list-languages-mapi.js +12 -4
  33. package/build/tools/list-taxonomy-groups-mapi.js +12 -4
  34. package/build/tools/list-workflows-mapi.js +1 -1
  35. package/build/tools/patch-content-type-mapi.js +3 -50
  36. package/build/tools/publish-variant-mapi.js +5 -8
  37. package/build/tools/search-variants-mapi.js +3 -29
  38. package/build/tools/unpublish-variant-mapi.js +5 -8
  39. package/build/tools/update-content-item-mapi.js +4 -4
  40. package/build/tools/upsert-language-variant-mapi.js +7 -10
  41. package/build/utils/responseHelper.js +90 -6
  42. package/package.json +4 -1
@@ -0,0 +1,450 @@
1
+ import * as assert from "node:assert";
2
+ import { describe, it } from "mocha";
3
+ import { createMcpToolSuccessResponse, createVariantMcpToolSuccessResponse, isEmptyOrDefault, removeEmptyElementsFromVariant, removeEmptyValues, } from "../../utils/responseHelper.js";
4
+ describe("isEmptyOrDefault", () => {
5
+ describe("should return true for empty/default values", () => {
6
+ it("returns true for null", () => {
7
+ assert.strictEqual(isEmptyOrDefault(null), true);
8
+ });
9
+ it("returns true for undefined", () => {
10
+ assert.strictEqual(isEmptyOrDefault(undefined), true);
11
+ });
12
+ it("returns true for empty string", () => {
13
+ assert.strictEqual(isEmptyOrDefault(""), true);
14
+ });
15
+ it("returns true for rich text empty paragraph", () => {
16
+ assert.strictEqual(isEmptyOrDefault("<p><br/></p>"), true);
17
+ });
18
+ it("returns true for empty array", () => {
19
+ assert.strictEqual(isEmptyOrDefault([]), true);
20
+ });
21
+ it("returns true for empty object", () => {
22
+ assert.strictEqual(isEmptyOrDefault({}), true);
23
+ });
24
+ });
25
+ describe("should return false for non-empty values", () => {
26
+ it("returns false for non-empty string", () => {
27
+ assert.strictEqual(isEmptyOrDefault("hello"), false);
28
+ });
29
+ it("returns false for number zero", () => {
30
+ assert.strictEqual(isEmptyOrDefault(0), false);
31
+ });
32
+ it("returns false for positive number", () => {
33
+ assert.strictEqual(isEmptyOrDefault(42), false);
34
+ });
35
+ it("returns false for boolean false", () => {
36
+ assert.strictEqual(isEmptyOrDefault(false), false);
37
+ });
38
+ it("returns false for boolean true", () => {
39
+ assert.strictEqual(isEmptyOrDefault(true), false);
40
+ });
41
+ it("returns false for non-empty array", () => {
42
+ assert.strictEqual(isEmptyOrDefault([1, 2, 3]), false);
43
+ });
44
+ it("returns false for non-empty object", () => {
45
+ assert.strictEqual(isEmptyOrDefault({ key: "value" }), false);
46
+ });
47
+ it("returns false for Date object", () => {
48
+ assert.strictEqual(isEmptyOrDefault(new Date()), false);
49
+ });
50
+ it("returns false for rich text with actual content", () => {
51
+ assert.strictEqual(isEmptyOrDefault("<p>Hello world</p>"), false);
52
+ });
53
+ });
54
+ });
55
+ describe("removeEmptyValues", () => {
56
+ describe("primitive values", () => {
57
+ it("returns undefined for null", () => {
58
+ assert.strictEqual(removeEmptyValues(null), undefined);
59
+ });
60
+ it("returns undefined for undefined", () => {
61
+ assert.strictEqual(removeEmptyValues(undefined), undefined);
62
+ });
63
+ it("returns undefined for empty string", () => {
64
+ assert.strictEqual(removeEmptyValues(""), undefined);
65
+ });
66
+ it("returns undefined for rich text empty paragraph", () => {
67
+ assert.strictEqual(removeEmptyValues("<p><br/></p>"), undefined);
68
+ });
69
+ it("preserves non-empty string", () => {
70
+ assert.strictEqual(removeEmptyValues("hello"), "hello");
71
+ });
72
+ it("preserves number zero", () => {
73
+ assert.strictEqual(removeEmptyValues(0), 0);
74
+ });
75
+ it("preserves boolean false", () => {
76
+ assert.strictEqual(removeEmptyValues(false), false);
77
+ });
78
+ });
79
+ describe("arrays", () => {
80
+ it("returns undefined for empty array", () => {
81
+ assert.strictEqual(removeEmptyValues([]), undefined);
82
+ });
83
+ it("removes empty values from array", () => {
84
+ assert.deepStrictEqual(removeEmptyValues([1, null, 2, "", 3]), [1, 2, 3]);
85
+ });
86
+ it("returns undefined when all array items are empty", () => {
87
+ assert.strictEqual(removeEmptyValues([null, "", [], {}]), undefined);
88
+ });
89
+ it("recursively cleans nested arrays", () => {
90
+ assert.deepStrictEqual(removeEmptyValues([1, [2, null, 3], [null, ""]]), [
91
+ 1,
92
+ [2, 3],
93
+ ]);
94
+ });
95
+ });
96
+ describe("objects", () => {
97
+ it("returns undefined for empty object", () => {
98
+ assert.strictEqual(removeEmptyValues({}), undefined);
99
+ });
100
+ it("removes null properties", () => {
101
+ assert.deepStrictEqual(removeEmptyValues({ a: 1, b: null }), { a: 1 });
102
+ });
103
+ it("removes undefined properties", () => {
104
+ assert.deepStrictEqual(removeEmptyValues({ a: 1, b: undefined }), {
105
+ a: 1,
106
+ });
107
+ });
108
+ it("removes empty string properties", () => {
109
+ assert.deepStrictEqual(removeEmptyValues({ a: 1, b: "" }), { a: 1 });
110
+ });
111
+ it("removes empty array properties", () => {
112
+ assert.deepStrictEqual(removeEmptyValues({ a: 1, b: [] }), { a: 1 });
113
+ });
114
+ it("removes empty object properties", () => {
115
+ assert.deepStrictEqual(removeEmptyValues({ a: 1, b: {} }), { a: 1 });
116
+ });
117
+ it("removes rich text empty paragraph properties", () => {
118
+ assert.deepStrictEqual(removeEmptyValues({ a: 1, b: "<p><br/></p>" }), {
119
+ a: 1,
120
+ });
121
+ });
122
+ it("returns undefined when all properties are empty", () => {
123
+ assert.strictEqual(removeEmptyValues({ a: null, b: "", c: [], d: {} }), undefined);
124
+ });
125
+ });
126
+ describe("nested structures", () => {
127
+ it("recursively cleans nested objects", () => {
128
+ const input = {
129
+ level1: {
130
+ level2: {
131
+ value: "keep",
132
+ empty: null,
133
+ },
134
+ emptyObj: {},
135
+ },
136
+ };
137
+ const expected = {
138
+ level1: {
139
+ level2: {
140
+ value: "keep",
141
+ },
142
+ },
143
+ };
144
+ assert.deepStrictEqual(removeEmptyValues(input), expected);
145
+ });
146
+ it("removes nested objects that become empty after cleaning", () => {
147
+ const input = {
148
+ keep: "value",
149
+ remove: {
150
+ nested: {
151
+ empty: null,
152
+ },
153
+ },
154
+ };
155
+ const expected = { keep: "value" };
156
+ assert.deepStrictEqual(removeEmptyValues(input), expected);
157
+ });
158
+ it("handles deeply nested structures", () => {
159
+ const input = {
160
+ a: {
161
+ b: {
162
+ c: {
163
+ d: {
164
+ value: "deep",
165
+ empty: "",
166
+ },
167
+ },
168
+ },
169
+ },
170
+ };
171
+ const expected = {
172
+ a: {
173
+ b: {
174
+ c: {
175
+ d: {
176
+ value: "deep",
177
+ },
178
+ },
179
+ },
180
+ },
181
+ };
182
+ assert.deepStrictEqual(removeEmptyValues(input), expected);
183
+ });
184
+ });
185
+ });
186
+ describe("removeEmptyElementsFromVariant", () => {
187
+ describe("non-object inputs", () => {
188
+ it("returns null for null", () => {
189
+ assert.strictEqual(removeEmptyElementsFromVariant(null), null);
190
+ });
191
+ it("returns undefined for undefined", () => {
192
+ assert.strictEqual(removeEmptyElementsFromVariant(undefined), undefined);
193
+ });
194
+ it("returns primitive values unchanged", () => {
195
+ assert.strictEqual(removeEmptyElementsFromVariant("string"), "string");
196
+ assert.strictEqual(removeEmptyElementsFromVariant(42), 42);
197
+ assert.strictEqual(removeEmptyElementsFromVariant(true), true);
198
+ });
199
+ });
200
+ describe("elements array filtering", () => {
201
+ it("removes elements with only element property", () => {
202
+ const input = {
203
+ elements: [{ element: { id: "id1" } }, { element: { id: "id2" } }],
204
+ };
205
+ const expected = {};
206
+ assert.deepStrictEqual(removeEmptyElementsFromVariant(input), expected);
207
+ });
208
+ it("keeps elements with value property", () => {
209
+ const input = {
210
+ elements: [{ element: { id: "id1" }, value: "content" }],
211
+ };
212
+ const expected = {
213
+ elements: [{ element: { id: "id1" }, value: "content" }],
214
+ };
215
+ assert.deepStrictEqual(removeEmptyElementsFromVariant(input), expected);
216
+ });
217
+ it("filters mixed elements array", () => {
218
+ const input = {
219
+ elements: [
220
+ { element: { id: "id1" } },
221
+ { element: { id: "id2" }, value: "keep" },
222
+ { element: { id: "id3" } },
223
+ { element: { id: "id4" }, components: [] },
224
+ ],
225
+ };
226
+ const expected = {
227
+ elements: [
228
+ { element: { id: "id2" }, value: "keep" },
229
+ { element: { id: "id4" }, components: [] },
230
+ ],
231
+ };
232
+ assert.deepStrictEqual(removeEmptyElementsFromVariant(input), expected);
233
+ });
234
+ it("keeps non-object elements in elements array", () => {
235
+ const input = {
236
+ elements: ["string", 42, { element: { id: "id1" } }],
237
+ };
238
+ const expected = {
239
+ elements: ["string", 42],
240
+ };
241
+ assert.deepStrictEqual(removeEmptyElementsFromVariant(input), expected);
242
+ });
243
+ });
244
+ describe("nested structures", () => {
245
+ it("processes nested objects with elements arrays", () => {
246
+ const input = {
247
+ item: {
248
+ elements: [{ element: { id: "id1" } }],
249
+ },
250
+ };
251
+ const expected = {
252
+ item: {},
253
+ };
254
+ assert.deepStrictEqual(removeEmptyElementsFromVariant(input), expected);
255
+ });
256
+ it("processes arrays of objects with elements", () => {
257
+ const input = {
258
+ variants: [
259
+ { elements: [{ element: { id: "id1" } }] },
260
+ { elements: [{ element: { id: "id2" }, value: "keep" }] },
261
+ ],
262
+ };
263
+ const expected = {
264
+ variants: [
265
+ {},
266
+ { elements: [{ element: { id: "id2" }, value: "keep" }] },
267
+ ],
268
+ };
269
+ assert.deepStrictEqual(removeEmptyElementsFromVariant(input), expected);
270
+ });
271
+ });
272
+ describe("preserves other properties", () => {
273
+ it("keeps non-elements properties unchanged", () => {
274
+ const input = {
275
+ id: "123",
276
+ name: "Test",
277
+ elements: [{ element: { id: "id1" } }],
278
+ };
279
+ const expected = {
280
+ id: "123",
281
+ name: "Test",
282
+ };
283
+ assert.deepStrictEqual(removeEmptyElementsFromVariant(input), expected);
284
+ });
285
+ });
286
+ });
287
+ describe("createMcpToolSuccessResponse", () => {
288
+ it("returns correct structure", () => {
289
+ const response = createMcpToolSuccessResponse({ key: "value" });
290
+ assert.strictEqual(response.content.length, 1);
291
+ assert.strictEqual(response.content[0].type, "text");
292
+ });
293
+ it("removes empty values from response data", () => {
294
+ const input = {
295
+ id: "123",
296
+ name: null,
297
+ value: "",
298
+ items: [],
299
+ };
300
+ const response = createMcpToolSuccessResponse(input);
301
+ const parsed = JSON.parse(response.content[0].text);
302
+ assert.deepStrictEqual(parsed, { id: "123" });
303
+ });
304
+ it("handles complex nested data", () => {
305
+ const input = {
306
+ contentType: {
307
+ id: "type-1",
308
+ name: "Article",
309
+ elements: [
310
+ { id: "el-1", name: "Title", codename: "" },
311
+ { id: "el-2", name: null, codename: "body" },
312
+ ],
313
+ },
314
+ };
315
+ const response = createMcpToolSuccessResponse(input);
316
+ const parsed = JSON.parse(response.content[0].text);
317
+ assert.deepStrictEqual(parsed, {
318
+ contentType: {
319
+ id: "type-1",
320
+ name: "Article",
321
+ elements: [
322
+ { id: "el-1", name: "Title" },
323
+ { id: "el-2", codename: "body" },
324
+ ],
325
+ },
326
+ });
327
+ });
328
+ });
329
+ describe("createVariantMcpToolSuccessResponse", () => {
330
+ it("returns correct structure", () => {
331
+ const response = createVariantMcpToolSuccessResponse({ key: "value" });
332
+ assert.strictEqual(response.content.length, 1);
333
+ assert.strictEqual(response.content[0].type, "text");
334
+ });
335
+ it("removes empty values and empty elements", () => {
336
+ const input = {
337
+ item: { id: "item-1" },
338
+ language: { id: "lang-1" },
339
+ elements: [
340
+ { element: { id: "el-1" }, value: "" },
341
+ { element: { id: "el-2" }, value: "content" },
342
+ { element: { id: "el-3" }, value: null },
343
+ ],
344
+ };
345
+ const response = createVariantMcpToolSuccessResponse(input);
346
+ const parsed = JSON.parse(response.content[0].text);
347
+ assert.deepStrictEqual(parsed, {
348
+ item: { id: "item-1" },
349
+ language: { id: "lang-1" },
350
+ elements: [{ element: { id: "el-2" }, value: "content" }],
351
+ });
352
+ });
353
+ it("removes elements array when all elements become empty", () => {
354
+ const input = {
355
+ item: { id: "item-1" },
356
+ elements: [
357
+ { element: { id: "el-1" }, value: "" },
358
+ { element: { id: "el-2" }, value: null },
359
+ { element: { id: "el-3" }, value: "<p><br/></p>" },
360
+ ],
361
+ };
362
+ const response = createVariantMcpToolSuccessResponse(input);
363
+ const parsed = JSON.parse(response.content[0].text);
364
+ assert.deepStrictEqual(parsed, {
365
+ item: { id: "item-1" },
366
+ });
367
+ });
368
+ it("handles real-world variant response", () => {
369
+ const input = {
370
+ item: { id: "f4b3fc05-e988-4dae-9ac1-a94aba566474" },
371
+ language: { id: "d1f95fde-af02-b3b5-bd9e-f232311ccab8" },
372
+ last_modified: "2018-02-27T19:08:25.404Z",
373
+ workflow: {
374
+ workflow_identifier: { id: "00000000-0000-0000-0000-000000000000" },
375
+ step_identifier: { id: "c199950d-99f0-4983-b711-6c4c91624b22" },
376
+ },
377
+ elements: [
378
+ { element: { id: "text-id" }, value: "Article Title" },
379
+ { element: { id: "rich-id" }, value: "<p><br/></p>", components: [] },
380
+ { element: { id: "number-id" }, value: null },
381
+ { element: { id: "assets-id" }, value: [] },
382
+ { element: { id: "taxonomy-id" }, value: [] },
383
+ ],
384
+ };
385
+ const response = createVariantMcpToolSuccessResponse(input);
386
+ const parsed = JSON.parse(response.content[0].text);
387
+ assert.deepStrictEqual(parsed, {
388
+ item: { id: "f4b3fc05-e988-4dae-9ac1-a94aba566474" },
389
+ language: { id: "d1f95fde-af02-b3b5-bd9e-f232311ccab8" },
390
+ last_modified: "2018-02-27T19:08:25.404Z",
391
+ workflow: {
392
+ workflow_identifier: { id: "00000000-0000-0000-0000-000000000000" },
393
+ step_identifier: { id: "c199950d-99f0-4983-b711-6c4c91624b22" },
394
+ },
395
+ elements: [{ element: { id: "text-id" }, value: "Article Title" }],
396
+ });
397
+ });
398
+ it("handles variant with multiple variants (filter-variants response)", () => {
399
+ const input = {
400
+ variants: [
401
+ {
402
+ item: { id: "item-1" },
403
+ elements: [
404
+ { element: { id: "el-1" }, value: "" },
405
+ { element: { id: "el-2" }, value: "content" },
406
+ ],
407
+ },
408
+ {
409
+ item: { id: "item-2" },
410
+ elements: [
411
+ { element: { id: "el-1" }, value: null },
412
+ { element: { id: "el-2" }, value: [] },
413
+ ],
414
+ },
415
+ ],
416
+ pagination: {
417
+ continuation_token: null,
418
+ next_page: "",
419
+ },
420
+ };
421
+ const response = createVariantMcpToolSuccessResponse(input);
422
+ const parsed = JSON.parse(response.content[0].text);
423
+ assert.deepStrictEqual(parsed, {
424
+ variants: [
425
+ {
426
+ item: { id: "item-1" },
427
+ elements: [{ element: { id: "el-2" }, value: "content" }],
428
+ },
429
+ {
430
+ item: { id: "item-2" },
431
+ },
432
+ ],
433
+ });
434
+ });
435
+ });
436
+ describe("variant with all empty elements", () => {
437
+ it("removes elements array entirely when all elements become empty", () => {
438
+ const input = {
439
+ elements: [
440
+ { element: { id: "text-id" }, value: "" },
441
+ { element: { id: "rich-id" }, value: "<p><br/></p>", components: [] },
442
+ { element: { id: "number-id" }, value: null },
443
+ { element: { id: "assets-id" }, value: [] },
444
+ ],
445
+ };
446
+ const response = createVariantMcpToolSuccessResponse(input);
447
+ const result = JSON.parse(response.content[0].text);
448
+ assert.deepStrictEqual(result, {}, "All empty elements should be removed, resulting in an empty object");
449
+ });
450
+ });
@@ -3,27 +3,20 @@ import { createMapiClient } from "../clients/kontentClients.js";
3
3
  import { handleMcpToolError } from "../utils/errorHandler.js";
4
4
  import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
5
5
  export const registerTool = (server) => {
6
- server.tool("add-content-item-mapi", "Add new Kontent.ai content item via Management API. This creates the content item structure but does not add content to language variants. Use upsert-language-variant-mapi to add content to the item.", {
7
- name: z
8
- .string()
9
- .min(1)
10
- .max(200)
11
- .describe("Display name of the content item (1-200 characters)"),
6
+ server.tool("add-content-item-mapi", "Add new Kontent.ai content item (creates structure only, use upsert-language-variant-mapi for content)", {
7
+ name: z.string().min(1).max(200).describe("Item name (1-200 chars)"),
12
8
  type: z
13
9
  .object({
14
10
  id: z.string().optional(),
15
11
  codename: z.string().optional(),
16
12
  external_id: z.string().optional(),
17
13
  })
18
- .describe("Reference to the content type by id, codename, or external_id. At least one property must be specified."),
14
+ .describe("Content type reference"),
19
15
  codename: z
20
16
  .string()
21
17
  .optional()
22
- .describe("Codename of the content item (optional, will be generated from name if not provided)"),
23
- external_id: z
24
- .string()
25
- .optional()
26
- .describe("External ID for the content item (optional, useful for external system integration)"),
18
+ .describe("Codename (auto-generated if omitted)"),
19
+ external_id: z.string().optional().describe("External ID"),
27
20
  collection: z
28
21
  .object({
29
22
  id: z.string().optional(),
@@ -31,7 +24,7 @@ export const registerTool = (server) => {
31
24
  external_id: z.string().optional(),
32
25
  })
33
26
  .optional()
34
- .describe("Reference to a collection by id, codename, or external_id (optional)"),
27
+ .describe("Collection reference"),
35
28
  }, async ({ name, type, codename, external_id, collection }, { authInfo: { token, clientId } = {} }) => {
36
29
  const client = createMapiClient(clientId, token);
37
30
  try {
@@ -4,23 +4,18 @@ import { contentGroupSchema, elementSchema, } from "../schemas/contentTypeSchema
4
4
  import { handleMcpToolError } from "../utils/errorHandler.js";
5
5
  import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
6
6
  export const registerTool = (server) => {
7
- server.tool("add-content-type-mapi", "Add new Kontent.ai content type via Management API", {
8
- name: z.string().describe("Display name of the content type"),
7
+ server.tool("add-content-type-mapi", "Add new Kontent.ai content type", {
8
+ name: z.string().describe("Content type name"),
9
9
  codename: z
10
10
  .string()
11
11
  .optional()
12
- .describe("Codename of the content type (optional, will be generated if not provided)"),
13
- external_id: z
14
- .string()
15
- .optional()
16
- .describe("External ID of the content type (optional)"),
17
- elements: z
18
- .array(elementSchema)
19
- .describe("Array of elements that define the structure of the content type"),
12
+ .describe("Codename (auto-generated if omitted)"),
13
+ external_id: z.string().optional().describe("External ID"),
14
+ elements: z.array(elementSchema).describe("Elements defining structure"),
20
15
  content_groups: z
21
16
  .array(contentGroupSchema)
22
17
  .optional()
23
- .describe("Array of content groups (optional)"),
18
+ .describe("Content groups"),
24
19
  }, async ({ name, codename, external_id, elements, content_groups }, { authInfo: { token, clientId } = {} }) => {
25
20
  const client = createMapiClient(clientId, token);
26
21
  try {
@@ -4,19 +4,16 @@ import { snippetElementSchema } from "../schemas/contentTypeSchemas.js";
4
4
  import { handleMcpToolError } from "../utils/errorHandler.js";
5
5
  import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
6
6
  export const registerTool = (server) => {
7
- server.tool("add-content-type-snippet-mapi", "Add new Kontent.ai content type snippet via Management API", {
8
- name: z.string().describe("Display name of the content type snippet"),
7
+ server.tool("add-content-type-snippet-mapi", "Add new Kontent.ai content type snippet", {
8
+ name: z.string().describe("Snippet name"),
9
9
  codename: z
10
10
  .string()
11
11
  .optional()
12
- .describe("Codename of the content type snippet (optional, will be generated if not provided)"),
13
- external_id: z
14
- .string()
15
- .optional()
16
- .describe("External ID of the content type snippet (optional)"),
12
+ .describe("Codename (auto-generated if omitted)"),
13
+ external_id: z.string().optional().describe("External ID"),
17
14
  elements: z
18
15
  .array(snippetElementSchema)
19
- .describe("Array of elements that define the structure of the content type snippet"),
16
+ .describe("Elements defining structure"),
20
17
  }, async ({ name, codename, external_id, elements }, { authInfo: { token, clientId } = {} }) => {
21
18
  const client = createMapiClient(clientId, token);
22
19
  try {
@@ -3,7 +3,7 @@ import { taxonomyGroupSchemas } from "../schemas/taxonomySchemas.js";
3
3
  import { handleMcpToolError } from "../utils/errorHandler.js";
4
4
  import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
5
5
  export const registerTool = (server) => {
6
- server.tool("add-taxonomy-group-mapi", "Add new Kontent.ai taxonomy group via Management API", taxonomyGroupSchemas, async (taxonomyGroup, { authInfo: { token, clientId } = {} }) => {
6
+ server.tool("add-taxonomy-group-mapi", "Add new Kontent.ai taxonomy group", taxonomyGroupSchemas, async (taxonomyGroup, { authInfo: { token, clientId } = {} }) => {
7
7
  const client = createMapiClient(clientId, token);
8
8
  try {
9
9
  const response = await client
@@ -3,23 +3,14 @@ import { createMapiClient } from "../clients/kontentClients.js";
3
3
  import { handleMcpToolError } from "../utils/errorHandler.js";
4
4
  import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
5
5
  export const registerTool = (server) => {
6
- server.tool("change-variant-workflow-step-mapi", "Change the workflow step of a language variant in Kontent.ai. This operation moves a language variant to a different step in the workflow, enabling content lifecycle management such as moving content from draft to review, review to published, etc.", {
7
- itemId: z
8
- .string()
9
- .uuid()
10
- .describe("Internal ID (UUID) of the content item whose language variant workflow step you want to change"),
6
+ server.tool("change-variant-workflow-step-mapi", "Change Kontent.ai variant workflow step", {
7
+ itemId: z.string().uuid().describe("Content item UUID"),
11
8
  languageId: z
12
9
  .string()
13
10
  .uuid()
14
- .describe("Internal ID (UUID) of the language variant. Use '00000000-0000-0000-0000-000000000000' for the default language"),
15
- workflowId: z
16
- .string()
17
- .uuid()
18
- .describe("Internal ID (UUID) of the workflow. This is the workflow that contains the target step"),
19
- workflowStepId: z
20
- .string()
21
- .uuid()
22
- .describe("Internal ID (UUID) of the target workflow step. This must be a valid step ID from the specified workflow. Common steps include Draft, Review, Published, and Archived, but the actual IDs depend on your specific workflow configuration"),
11
+ .describe("Language variant UUID (default: 00000000-0000-0000-0000-000000000000)"),
12
+ workflowId: z.string().uuid().describe("Workflow UUID"),
13
+ workflowStepId: z.string().uuid().describe("Target workflow step UUID"),
23
14
  }, async ({ itemId, languageId, workflowId, workflowStepId }, { authInfo: { token, clientId } = {} }) => {
24
15
  const client = createMapiClient(clientId, token);
25
16
  try {
@@ -153,4 +153,43 @@ All MCP tools have been optimized to work with internal IDs for maximum efficien
153
153
  - **search-variants-mapi**: Finds content WHERE THE MAIN TOPIC matches a concept (e.g., "articles about wildlife")
154
154
  - **filter-variants-mapi**: Finds SPECIFIC WORDS anywhere in content, regardless of topic (e.g., brand compliance, detecting prohibited terms in any content)
155
155
 
156
- See each tool's description for detailed usage guidelines.`;
156
+ See each tool's description for detailed usage guidelines.
157
+
158
+ ## Response Optimization
159
+
160
+ **IMPORTANT**: Empty values are automatically removed from ALL entity responses (content items, variants, content types, assets, taxonomies) to reduce token usage.
161
+
162
+ ### Empty Element Patterns in Variants
163
+
164
+ Elements with these values are removed entirely from variant responses:
165
+
166
+ | Element Type | Empty Value | Removed Form |
167
+ |--------------|------------|--------------|
168
+ | text | "" | {"element": {"id": "uuid"}, "value": ""} |
169
+ | rich_text | "<p><br/></p>" | {"element": {"id": "uuid"}, "value": "<p><br/></p>", "components": []} |
170
+ | number | null | {"element": {"id": "uuid"}, "value": null} |
171
+ | date_time | null | {"element": {"id": "uuid"}, "value": null, "display_timezone": null} |
172
+ | asset, modular_content, subpages, multiple_choice, taxonomy | [] | {"element": {"id": "uuid"}, "value": []} |
173
+ | url_slug | "" + autogenerated | {"element": {"id": "uuid"}, "mode": "autogenerated", "value": ""} |
174
+ | custom | null | {"element": {"id": "uuid"}, "value": null, "searchable_value": null} |
175
+
176
+ ### Understanding Missing Properties
177
+
178
+ **Missing property = has default/empty value across ALL entities:**
179
+
180
+ **Variants:**
181
+ - Missing element → See table above for defaults
182
+ - Components → Recursively optimized
183
+
184
+ **Content Types:**
185
+ - content_groups → []
186
+ - allowed_content_types → []
187
+ - allowed_blocks/formatting → []
188
+ - validation_regex → not active
189
+ - default values → null/""
190
+
191
+ **All Entities:**
192
+ - Empty arrays → []
193
+ - Empty objects → {}
194
+ - Null values → removed
195
+ - Empty strings → ""`;