@kontent-ai/mcp-server 0.12.0 → 0.14.0

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/README.md CHANGED
@@ -65,6 +65,8 @@ npx @kontent-ai/mcp-server@latest sse
65
65
  * **get-type-mapi** – Get Kontent.ai content type by internal ID from Management API
66
66
  * **list-content-types-mapi** – Get all Kontent.ai content types from Management API
67
67
  * **add-content-type-mapi** – Add new Kontent.ai content type via Management API
68
+ * **patch-content-type-mapi** – Update an existing Kontent.ai content type by codename using patch operations (move, addInto, remove, replace)
69
+ * **delete-content-type-mapi** – Delete a Kontent.ai content type by codename
68
70
 
69
71
  ### Content Type Snippet Management
70
72
 
@@ -159,6 +161,30 @@ Then configure your MCP client:
159
161
  }
160
162
  ```
161
163
 
164
+ ### 🌊 Streamable HTTP Transport
165
+
166
+ For Streamable HTTP transport, first start the server:
167
+
168
+ ```bash
169
+ npx @kontent-ai/mcp-server@latest shttp
170
+ ```
171
+
172
+ With environment variables in a `.env` file, or otherwise accessible to the process:
173
+ ```env
174
+ KONTENT_API_KEY=<management-api-key>
175
+ KONTENT_ENVIRONMENT_ID=<environment-id>
176
+ PORT=3001 # optional, defaults to 3001
177
+ ```
178
+
179
+ Then configure your MCP client:
180
+ ```json
181
+ {
182
+ "kontent-ai-http": {
183
+ "url": "http://localhost:3001/mcp"
184
+ }
185
+ }
186
+ ```
187
+
162
188
  ## 💻 Development
163
189
 
164
190
  ### 🛠 Local Installation
@@ -177,10 +203,12 @@ npm run build
177
203
  # Start the server
178
204
  npm run start:sse # For SSE transport
179
205
  npm run start:stdio # For STDIO transport
206
+ npm run start:shttp # For Streamable HTTP transport
180
207
 
181
208
  # Start the server with automatic reloading (no need to build first)
182
209
  npm run dev:sse # For SSE transport
183
210
  npm run dev:stdio # For STDIO transport
211
+ npm run dev:shttp # For Streamable HTTP transport
184
212
  ```
185
213
 
186
214
  ### 📂 Project Structure
package/build/bin.js CHANGED
@@ -1,11 +1,70 @@
1
1
  #!/usr/bin/env node
2
- import "dotenv/config";
3
2
  import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
4
3
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
5
+ import "dotenv/config";
5
6
  import express from "express";
6
7
  import packageJson from "../package.json" with { type: "json" };
7
8
  import { createServer } from "./server.js";
8
9
  const version = packageJson.version;
10
+ async function startStreamableHTTP() {
11
+ const app = express();
12
+ app.use(express.json());
13
+ app.post("/mcp", async (req, res) => {
14
+ try {
15
+ const { server } = createServer();
16
+ const transport = new StreamableHTTPServerTransport({
17
+ sessionIdGenerator: undefined,
18
+ });
19
+ res.on("close", () => {
20
+ console.log("Request closed");
21
+ transport.close();
22
+ server.close();
23
+ });
24
+ await server.connect(transport);
25
+ await transport.handleRequest(req, res, req.body);
26
+ }
27
+ catch (error) {
28
+ console.error("Error handling MCP request:", error);
29
+ if (!res.headersSent) {
30
+ res.status(500).json({
31
+ jsonrpc: "2.0",
32
+ error: {
33
+ code: -32603,
34
+ message: "Internal server error",
35
+ },
36
+ id: null,
37
+ });
38
+ }
39
+ }
40
+ });
41
+ app.get("/mcp", async (_, res) => {
42
+ console.log("Received GET MCP request");
43
+ res.writeHead(405).end(JSON.stringify({
44
+ jsonrpc: "2.0",
45
+ error: {
46
+ code: -32000,
47
+ message: "Method not allowed.",
48
+ },
49
+ id: null,
50
+ }));
51
+ });
52
+ app.delete("/mcp", async (_, res) => {
53
+ console.log("Received DELETE MCP request");
54
+ res.writeHead(405).end(JSON.stringify({
55
+ jsonrpc: "2.0",
56
+ error: {
57
+ code: -32000,
58
+ message: "Method not allowed.",
59
+ },
60
+ id: null,
61
+ }));
62
+ });
63
+ const PORT = process.env.PORT || 3001;
64
+ app.listen(PORT, () => {
65
+ console.log(`Kontent.ai MCP Server v${version} (Streamable HTTP) running on port ${PORT}`);
66
+ });
67
+ }
9
68
  async function startSSE() {
10
69
  const app = express();
11
70
  const { server } = createServer();
@@ -32,8 +91,10 @@ async function main() {
32
91
  const args = process.argv.slice(2);
33
92
  const transportType = args[0]?.toLowerCase();
34
93
  if (!transportType ||
35
- (transportType !== "stdio" && transportType !== "sse")) {
36
- console.error("Please specify a valid transport type: stdio or sse");
94
+ (transportType !== "stdio" &&
95
+ transportType !== "sse" &&
96
+ transportType !== "shttp")) {
97
+ console.error("Please specify a valid transport type: stdio, sse, or shttp");
37
98
  process.exit(1);
38
99
  }
39
100
  if (transportType === "stdio") {
@@ -42,6 +103,9 @@ async function main() {
42
103
  else if (transportType === "sse") {
43
104
  await startSSE();
44
105
  }
106
+ else if (transportType === "shttp") {
107
+ await startStreamableHTTP();
108
+ }
45
109
  }
46
110
  main().catch((error) => {
47
111
  console.error("Fatal error:", error);
@@ -1,6 +1,6 @@
1
1
  import { z } from "zod";
2
2
  // Define a reusable reference object schema
3
- const referenceObjectSchema = z
3
+ export const referenceObjectSchema = z
4
4
  .object({
5
5
  id: z.string().optional(),
6
6
  codename: z.string().optional(),
@@ -26,7 +26,7 @@ const namedElementSchema = {
26
26
  };
27
27
  // Limit schemas
28
28
  const conditionEnum = z.enum(["at_most", "exactly", "at_least"]);
29
- const countLimitSchema = z
29
+ export const countLimitSchema = z
30
30
  .object({
31
31
  value: z.number(),
32
32
  condition: conditionEnum,
@@ -47,21 +47,21 @@ const imageLimitSchema = {
47
47
  .optional(),
48
48
  };
49
49
  // Default value schemas
50
- const arrayDefaultSchema = z
50
+ export const arrayDefaultSchema = z
51
51
  .object({
52
52
  global: z.object({
53
53
  value: z.array(referenceObjectSchema),
54
54
  }),
55
55
  })
56
56
  .optional();
57
- const stringDefaultSchema = z
57
+ export const stringDefaultSchema = z
58
58
  .object({
59
59
  global: z.object({
60
60
  value: z.string(),
61
61
  }),
62
62
  })
63
63
  .optional();
64
- const numberDefaultSchema = z
64
+ export const numberDefaultSchema = z
65
65
  .object({
66
66
  global: z.object({
67
67
  value: z.number(),
@@ -69,7 +69,7 @@ const numberDefaultSchema = z
69
69
  })
70
70
  .optional();
71
71
  // Regex validation schema
72
- const regexValidationSchema = z
72
+ export const regexValidationSchema = z
73
73
  .object({
74
74
  is_active: z.boolean(),
75
75
  regex: z.string(),
@@ -78,7 +78,7 @@ const regexValidationSchema = z
78
78
  })
79
79
  .optional();
80
80
  // Text length limit schema
81
- const textLengthLimitSchema = z
81
+ export const textLengthLimitSchema = z
82
82
  .object({
83
83
  value: z.number(),
84
84
  applies_to: z.enum(["words", "characters"]),
@@ -133,15 +133,16 @@ const subpagesElementSchema = {
133
133
  .optional(),
134
134
  item_count_limit: countLimitSchema,
135
135
  };
136
+ export const optionSchema = z.object({
137
+ name: z.string(),
138
+ codename: z.string().optional(),
139
+ external_id: z.string().optional(),
140
+ });
136
141
  const multipleChoiceElementSchema = {
137
142
  type: z.literal("multiple_choice"),
138
143
  ...namedElementSchema,
139
144
  mode: z.enum(["single", "multiple"]),
140
- options: z.array(z.object({
141
- name: z.string(),
142
- codename: z.string().optional(),
143
- external_id: z.string().optional(),
144
- })),
145
+ options: z.array(optionSchema),
145
146
  default: arrayDefaultSchema.describe("Default value of the multiple choice element. Reference one of the options by its codename."),
146
147
  };
147
148
  const numberElementSchema = {
@@ -149,67 +150,78 @@ const numberElementSchema = {
149
150
  ...namedElementSchema,
150
151
  default: numberDefaultSchema,
151
152
  };
153
+ export const allowedBlockSchema = z.enum([
154
+ "images",
155
+ "text",
156
+ "tables",
157
+ "components-and-items",
158
+ ]);
159
+ export const allowedFormattingSchema = z.enum([
160
+ "unstyled",
161
+ "bold",
162
+ "italic",
163
+ "code",
164
+ "link",
165
+ "subscript",
166
+ "superscript",
167
+ ]);
168
+ export const allowedTextBlockSchema = z.enum([
169
+ "paragraph",
170
+ "heading-one",
171
+ "heading-two",
172
+ "heading-three",
173
+ "heading-four",
174
+ "heading-five",
175
+ "heading-six",
176
+ "ordered-list",
177
+ "unordered-list",
178
+ ]);
179
+ export const allowedTableBlockSchema = z.enum(["images", "text"]);
180
+ export const allowedTableFormattingSchema = z.enum([
181
+ "unstyled",
182
+ "bold",
183
+ "italic",
184
+ "code",
185
+ "link",
186
+ "subscript",
187
+ "superscript",
188
+ ]);
189
+ export const allowedTableTextBlockSchema = z.enum([
190
+ "paragraph",
191
+ "heading-one",
192
+ "heading-two",
193
+ "heading-three",
194
+ "heading-four",
195
+ "heading-five",
196
+ "heading-six",
197
+ "ordered-list",
198
+ "unordered-list",
199
+ ]);
152
200
  const richTextElementSchema = {
153
201
  type: z.literal("rich_text"),
154
202
  ...namedElementSchema,
155
203
  allowed_blocks: z
156
- .array(z.enum(["images", "text", "tables", "components-and-items"]))
204
+ .array(allowedBlockSchema)
157
205
  .optional()
158
206
  .describe("Specifies allowed blocks. Use an empty array to allow all options."),
159
207
  allowed_formatting: z
160
- .array(z.enum([
161
- "unstyled",
162
- "bold",
163
- "italic",
164
- "code",
165
- "link",
166
- "subscript",
167
- "superscript",
168
- ]))
208
+ .array(allowedFormattingSchema)
169
209
  .optional()
170
210
  .describe("Specifies allowed formatting options. Use an empty array to allow all options."),
171
211
  allowed_text_blocks: z
172
- .array(z.enum([
173
- "paragraph",
174
- "heading-one",
175
- "heading-two",
176
- "heading-three",
177
- "heading-four",
178
- "heading-five",
179
- "heading-six",
180
- "ordered-list",
181
- "unordered-list",
182
- ]))
212
+ .array(allowedTextBlockSchema)
183
213
  .optional()
184
214
  .describe("Specifies allowed text blocks. Use an empty array to allow all options."),
185
215
  allowed_table_blocks: z
186
- .array(z.enum(["images", "text"]))
216
+ .array(allowedTableBlockSchema)
187
217
  .optional()
188
218
  .describe("Specifies allowed table blocks. Use an empty array to allow all options."),
189
219
  allowed_table_formatting: z
190
- .array(z.enum([
191
- "unstyled",
192
- "bold",
193
- "italic",
194
- "code",
195
- "link",
196
- "subscript",
197
- "superscript",
198
- ]))
220
+ .array(allowedTableFormattingSchema)
199
221
  .optional()
200
222
  .describe("Specifies allowed table formatting options. Use an empty array to allow all options."),
201
223
  allowed_table_text_blocks: z
202
- .array(z.enum([
203
- "paragraph",
204
- "heading-one",
205
- "heading-two",
206
- "heading-three",
207
- "heading-four",
208
- "heading-five",
209
- "heading-six",
210
- "ordered-list",
211
- "unordered-list",
212
- ]))
224
+ .array(allowedTableTextBlockSchema)
213
225
  .optional()
214
226
  .describe("Specifies allowed table text blocks. Use an empty array to allow all options."),
215
227
  allowed_content_types: z
@@ -244,17 +256,16 @@ const textElementSchema = {
244
256
  validation_regex: regexValidationSchema,
245
257
  default: stringDefaultSchema,
246
258
  };
259
+ export const dependsOnSchema = z.object({
260
+ element: referenceObjectSchema.describe("An object with an id or codename property referencing an element."),
261
+ snippet: referenceObjectSchema
262
+ .describe("An object with an id or codename property referencing a content type snippet.")
263
+ .optional(),
264
+ });
247
265
  const urlSlugElementSchema = {
248
266
  type: z.literal("url_slug"),
249
267
  ...namedElementSchema,
250
- depends_on: z
251
- .object({
252
- element: referenceObjectSchema.describe("An object with an id or codename property referencing an element."),
253
- snippet: referenceObjectSchema
254
- .describe("An object with an id or codename property referencing a content type snippet.")
255
- .optional(),
256
- })
257
- .describe("The element the URL slug depends on. If this element is within a snippet, the snippet must also be specified."),
268
+ depends_on: dependsOnSchema.describe("The element the URL slug depends on. If this element is within a snippet, the snippet must also be specified."),
258
269
  validation_regex: regexValidationSchema,
259
270
  };
260
271
  // Define a union type of all possible element types for content types
@@ -0,0 +1,118 @@
1
+ // Patch operation schemas for content type modifications
2
+ // Based on: https://kontent.ai/learn/docs/apis/openapi/management-api-v2/#operation/modify-a-content-type
3
+ import { z } from "zod";
4
+ import { allowedBlockSchema, allowedFormattingSchema, allowedTableBlockSchema, allowedTableFormattingSchema, allowedTableTextBlockSchema, allowedTextBlockSchema, arrayDefaultSchema, contentGroupSchema, countLimitSchema, dependsOnSchema, elementSchema, numberDefaultSchema, optionSchema, referenceObjectSchema, regexValidationSchema, stringDefaultSchema, textLengthLimitSchema, } from "../contentTypeSchemas.js";
5
+ // Move operation - Move elements within content type
6
+ const moveOperationSchema = z.object({
7
+ op: z.literal("move"),
8
+ path: z
9
+ .string()
10
+ .describe(`Identifies the object you want to move using a path reference. The path reference should be in format 'id:{uuid}'. Examples:
11
+ • '/elements/id:123e4567-e89b-12d3-a456-426614174000' - Move an element
12
+ • '/elements/id:123e4567-e89b-12d3-a456-426614174000/options/id:987fcdeb-51a2-43d1-9f4e-123456789abc' - Move a multiple choice option (first reference is element, second is option)
13
+ • '/content_groups/id:456e7890-a12b-34c5-d678-901234567def' - Move a content group`),
14
+ before: referenceObjectSchema
15
+ .describe("A reference to the object before which you want to move the object. For example, to move an element before an existing element with id:uuid 'text', set before to {codename: 'text'}. The before and after properties are mutually exclusive.")
16
+ .optional(),
17
+ after: referenceObjectSchema
18
+ .describe("A reference to the object after which you want to move the object. For example, to move an element after an existing element with id:uuid 'Text', set after to {codename: 'text'}. The before and after properties are mutually exclusive.")
19
+ .optional(),
20
+ });
21
+ // AddInto operation - Add new elements to content type
22
+ const addIntoOperationSchema = z.object({
23
+ op: z.literal("addInto"),
24
+ path: z
25
+ .string()
26
+ .describe(`JSON Pointer path where to add the item. The path reference should be in format 'id:{uuid}'. Examples:
27
+ • '/elements' - Add a new element to the content type
28
+ • '/content_groups' - Add a new content group
29
+ • '/elements/id:123e4567-e89b-12d3-a456-426614174000/allowed_content_types' - Add allowed content type to rich text or linked items element
30
+ • '/elements/id:123e4567-e89b-12d3-a456-426614174000/allowed_elements' - Add allowed element to custom element
31
+ • '/elements/id:123e4567-e89b-12d3-a456-426614174000/options' - Add multiple choice option
32
+ • '/elements/id:123e4567-e89b-12d3-a456-426614174000/allowed_blocks' - Add block for rich text element
33
+ • '/elements/id:123e4567-e89b-12d3-a456-426614174000/allowed_formatting' - Add formatting option for rich text element
34
+ (Replace with actual element UUID)
35
+
36
+ - CRITICAL: When adding a url slug that references a snippet element, first add the snippet element to the content type if it’s not already included.`),
37
+ value: z
38
+ .union([
39
+ elementSchema,
40
+ optionSchema,
41
+ contentGroupSchema,
42
+ referenceObjectSchema,
43
+ allowedBlockSchema,
44
+ allowedFormattingSchema,
45
+ allowedTextBlockSchema,
46
+ allowedTableBlockSchema,
47
+ allowedTableFormattingSchema,
48
+ allowedTableTextBlockSchema,
49
+ z.string(),
50
+ z.number(),
51
+ z.boolean(),
52
+ z.null(),
53
+ z.any(),
54
+ ])
55
+ .describe("The item to add (element, content group, option, etc.)"),
56
+ });
57
+ // Remove operation - Remove elements from content type
58
+ const removeOperationSchema = z.object({
59
+ op: z.literal("remove"),
60
+ path: z
61
+ .string()
62
+ .describe(`JSON Pointer path to the item being removed. The path reference should be in format 'id:{uuid}'. Examples:
63
+ • '/elements/id:123e4567-e89b-12d3-a456-426614174000' - Remove an element
64
+ • '/elements/id:123e4567-e89b-12d3-a456-426614174000/allowed_content_types/id:987fcdeb-51a2-43d1-9f4e-123456789abc' - Remove allowed content type from rich text/linked items element
65
+ • '/elements/id:123e4567-e89b-12d3-a456-426614174000/allowed_element/id:456e7890-a12b-34c5-d678-901234567def' - Remove allowed element from custom element
66
+ • '/elements/id:123e4567-e89b-12d3-a456-426614174000/options/id:321dcba9-87f6-54e3-21b0-fedcba987654' - Remove multiple choice option
67
+ • '/content_groups/id:456e7890-a12b-34c5-d678-901234567def' - Remove content group (removes all elements within the group)
68
+ • '/elements/id:123e4567-e89b-12d3-a456-426614174000/allowed_blocks/images' - Remove rich-text element limitation (where {block} is the limitation type)
69
+ (Replace with actual UUIDs)`),
70
+ });
71
+ // Replace operation - Replace/update existing elements in content type
72
+ const replaceOperationSchema = z.object({
73
+ op: z.literal("replace"),
74
+ path: z
75
+ .string()
76
+ .describe(`JSON Pointer path to the item or property being replaced. The path reference should be in format 'id:{uuid}' Examples:
77
+ • '/name' - Change the content type's name
78
+ • '/codename' - Change the content type's codename
79
+ • '/content_groups/id:456e7890-a12b-34c5-d678-901234567def/name' - Change the name of a content group
80
+ • '/elements/id:123e4567-e89b-12d3-a456-426614174000/name' - Change an element property (property depends on element type)
81
+ • '/elements/id:123e4567-e89b-12d3-a456-426614174000/options/id:321dcba9-87f6-54e3-21b0-fedcba987654/name' - Change multiple choice option property (name or codename)
82
+ (Replace with actual element/group UUIDs)
83
+
84
+ REPLACE OPERATION RULES:
85
+ • CAN modify: Most element properties based on element type (name, guidelines, validation, etc.)
86
+ • CANNOT modify: external_id, id, or type of elements
87
+ • CANNOT replace individual object values - must replace the entire object at once
88
+ • FOR rich text elements: CANNOT replace individual items in allowed_blocks, allowed_formatting, allowed_text_blocks, allowed_table_blocks, allowed_table_formatting, allowed_table_text_blocks, allowed_content_types, allowed_item_link_types arrays - use addInto/remove operations instead for individual items
89
+ • FOR multiple choice options: Can modify name and codename properties`),
90
+ value: z
91
+ .union([
92
+ dependsOnSchema,
93
+ regexValidationSchema,
94
+ textLengthLimitSchema,
95
+ countLimitSchema,
96
+ arrayDefaultSchema,
97
+ stringDefaultSchema,
98
+ numberDefaultSchema,
99
+ z.string(),
100
+ z.number(),
101
+ z.boolean(),
102
+ z.null(),
103
+ z.any(), // in Union zod tries to match from top to down. any if there is something missing so agent dont fail on validation.
104
+ ])
105
+ .describe("The new value to replace the existing one"),
106
+ });
107
+ // Union type for all patch operations
108
+ export const patchOperationSchema = z.discriminatedUnion("op", [
109
+ moveOperationSchema,
110
+ addIntoOperationSchema,
111
+ removeOperationSchema,
112
+ replaceOperationSchema,
113
+ ]);
114
+ // Schema for array of patch operations
115
+ export const patchOperationsSchema = z
116
+ .array(patchOperationSchema)
117
+ .min(1)
118
+ .describe("Array of patch operations to apply to the content type. Must contain at least one operation.");
package/build/server.js CHANGED
@@ -6,6 +6,7 @@ import { registerTool as registerAddContentTypeSnippetMapi } from "./tools/add-c
6
6
  import { registerTool as registerAddTaxonomyGroupMapi } from "./tools/add-taxonomy-group-mapi.js";
7
7
  import { registerTool as registerChangeVariantWorkflowStepMapi } from "./tools/change-variant-workflow-step-mapi.js";
8
8
  import { registerTool as registerDeleteContentItemMapi } from "./tools/delete-content-item-mapi.js";
9
+ import { registerTool as registerDeleteContentTypeMapi } from "./tools/delete-content-type-mapi.js";
9
10
  import { registerTool as registerDeleteLanguageVariantMapi } from "./tools/delete-language-variant-mapi.js";
10
11
  import { registerTool as registerFilterVariantsMapi } from "./tools/filter-variants-mapi.js";
11
12
  import { registerTool as registerGetAssetMapi } from "./tools/get-asset-mapi.js";
@@ -22,6 +23,7 @@ import { registerTool as registerListContentTypesMapi } from "./tools/list-conte
22
23
  import { registerTool as registerListLanguagesMapi } from "./tools/list-languages-mapi.js";
23
24
  import { registerTool as registerListTaxonomyGroupsMapi } from "./tools/list-taxonomy-groups-mapi.js";
24
25
  import { registerTool as registerListWorkflowsMapi } from "./tools/list-workflows-mapi.js";
26
+ import { registerTool as registerPatchContentTypeMapi } from "./tools/patch-content-type-mapi.js";
25
27
  import { registerTool as registerPublishVariantMapi } from "./tools/publish-variant-mapi.js";
26
28
  import { registerTool as registerUnpublishVariantMapi } from "./tools/unpublish-variant-mapi.js";
27
29
  import { registerTool as registerUpdateContentItemMapi } from "./tools/update-content-item-mapi.js";
@@ -43,10 +45,12 @@ export const createServer = () => {
43
45
  registerGetVariantMapi(server);
44
46
  registerGetTypeMapi(server);
45
47
  registerListContentTypesMapi(server);
48
+ registerDeleteContentTypeMapi(server);
46
49
  registerListLanguagesMapi(server);
47
50
  registerGetAssetMapi(server);
48
51
  registerListAssetsMapi(server);
49
52
  registerAddContentTypeMapi(server);
53
+ registerPatchContentTypeMapi(server);
50
54
  registerAddContentTypeSnippetMapi(server);
51
55
  registerGetTypeSnippetMapi(server);
52
56
  registerListContentTypeSnippetsMapi(server);
@@ -0,0 +1,24 @@
1
+ import { z } from "zod";
2
+ import { createMapiClient } from "../clients/kontentClients.js";
3
+ import { handleMcpToolError } from "../utils/errorHandler.js";
4
+ import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
5
+ export const registerTool = (server) => {
6
+ server.tool("delete-content-type-mapi", "Delete a content type by codename from Management API", {
7
+ codename: z.string().describe("Codename of the content type to delete"),
8
+ }, async ({ codename }) => {
9
+ const client = createMapiClient();
10
+ try {
11
+ const response = await client
12
+ .deleteContentType()
13
+ .byTypeCodename(codename)
14
+ .toPromise();
15
+ return createMcpToolSuccessResponse({
16
+ message: `Content type '${codename}' deleted successfully`,
17
+ deletedType: response.data,
18
+ });
19
+ }
20
+ catch (error) {
21
+ return handleMcpToolError(error, "Content Type Deletion");
22
+ }
23
+ });
24
+ };
@@ -0,0 +1,76 @@
1
+ import { z } from "zod";
2
+ import { createMapiClient } from "../clients/kontentClients.js";
3
+ import { patchOperationsSchema } from "../schemas/patchSchemas/contentTypePatchSchemas.js";
4
+ import { handleMcpToolError } from "../utils/errorHandler.js";
5
+ import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
6
+ export const registerTool = (server) => {
7
+ server.tool("patch-content-type-mapi", "Update an existing Kontent.ai content type by codename via Management API. Supports move, addInto, remove, and replace operations following RFC 6902 JSON Patch specification.", {
8
+ codename: z.string().describe("Codename of the content type to update"),
9
+ operations: patchOperationsSchema.describe(`Array of patch operations to apply. Supports: 'move' (reorganize elements), 'addInto' (add new elements), 'remove' (delete elements), 'replace' (update existing elements/properties).
10
+
11
+ CRITICAL REQUIREMENTS:
12
+ - ALWAYS call get-type-mapi tool before patching to get the latest content type schema
13
+ - Use addInto/remove for array operations (adding/removing items from arrays like elements and element's array properties - allowed_content_types, allowed_item_link_types, allowed_blocks, allowed_text_blocks, allowed_formatting, allowed_table_blocks, allowed_table_text_blocks, allowed_table_formatting)
14
+ - Never ever use replace for element's array properties - use addInto/remove instead
15
+ - Use replace for element's primitive data types and object properties (maximum_text_length, validation_regex, etc.)
16
+ - External_id and type cannot be modified after creation
17
+ - Patch operations with URL Slug elements referencing snippet elements MUST ensure snippet is present first
18
+ - When adding to allowed_formatting or allowed_table_formatting, 'unstyled' must be the first item in the array. If it is not present, then add it as first operation.
19
+
20
+ RICH TEXT ELEMENT PROPERTIES:
21
+ - allowed_content_types: Array of content type references. Specifies allowed content types for components and linked items. Empty array allows all.
22
+ - allowed_item_link_types: Array of content type references. Specifies content types allowed in text links (applies to text and tables). Empty array allows all.
23
+ - allowed_blocks: Available options: "images", "text", "tables", "components-and-items". Empty array allows all blocks.
24
+ - allowed_image_types: Available options: "adjustable" (only transformable images), "any" (all image files).
25
+ - allowed_text_blocks: Available options: "paragraph", "heading-one", "heading-two", "heading-three", "heading-four", "heading-five", "heading-six", "ordered-list", "unordered-list". Empty array allows all.
26
+ - allowed_formatting: Available options: "unstyled", "bold", "italic", "code", "link", "subscript", "superscript". "unstyled" must be first if used. Empty array allows all.
27
+ - allowed_table_blocks: Available options: "images", "text". Use ["text"] for text-only or empty array for both text and images.
28
+ - allowed_table_text_blocks: Available options: "paragraph", "heading-one", "heading-two", "heading-three", "heading-four", "heading-five", "heading-six", "ordered-list", "unordered-list". Empty array allows all.
29
+ - allowed_table_formatting: Available options: "unstyled", "bold", "italic", "code", "link", "subscript", "superscript". "unstyled" must be first if used. Empty array allows all.
30
+
31
+ OPERATION TYPES:
32
+ 1. move: Reorganize elements, options, or content groups. Uses 'before' or 'after' reference.
33
+ 2. addInto: Add new elements, options, content groups, or array items. Use for all array operations.
34
+ 3. remove: Remove elements, options, content groups, or array items. Use for all array removals.
35
+ 4. replace: Update existing properties of primitive data types and objects. Cannot modify external_id, id, or type.
36
+
37
+ PATH FORMATS:
38
+ - Use JSON Pointer paths with id:{uuid} format for referencing objects
39
+ - Element: /elements/id:123e4567-e89b-12d3-a456-426614174000
40
+ - Element property: /elements/id:123e4567-e89b-12d3-a456-426614174000/name
41
+ - Multiple choice option: /elements/id:123e4567-e89b-12d3-a456-426614174000/options/id:987fcdeb-51a2-43d1-9f4e-123456789abc
42
+ - Content group: /content_groups/id:456e7890-a12b-34c5-d678-901234567def
43
+ - Rich text array property: /elements/id:123e4567-e89b-12d3-a456-426614174000/allowed_content_types/id:987fcdeb-51a2-43d1-9f4e-123456789abc
44
+
45
+ SPECIAL TECHNIQUES:
46
+ - To remove content groups while keeping elements: Set ALL elements' content_group to null AND remove ALL content groups in ONE request (atomic operation)
47
+ - URL Slug with snippet dependency: First add snippet element, then add URL slug with depends_on reference
48
+
49
+ BEST PRACTICES:
50
+ - Use descriptive codenames following naming conventions
51
+ - Group related operations in a single patch request when possible
52
+ - Use proper reference formats (id:{uuid}) in paths
53
+ - Validate element dependencies before adding URL slug elements
54
+ - Consider element ordering when using move operations
55
+ - Use atomic operations for complex changes like removing content groups
56
+ - When adding to allowed_formatting or allowed_table_formatting, always ensure 'unstyled' is the first item in the array`),
57
+ }, async ({ codename, operations }) => {
58
+ const client = createMapiClient();
59
+ try {
60
+ // Apply patch operations using the modifyContentType method
61
+ const response = await client
62
+ .modifyContentType()
63
+ .byTypeCodename(codename)
64
+ .withData(operations)
65
+ .toPromise();
66
+ return createMcpToolSuccessResponse({
67
+ message: `Content type '${codename}' updated successfully with ${operations.length} operation(s)`,
68
+ contentType: response.rawData,
69
+ appliedOperations: operations,
70
+ });
71
+ }
72
+ catch (error) {
73
+ return handleMcpToolError(error, `Content Type Patch`);
74
+ }
75
+ });
76
+ };
package/package.json CHANGED
@@ -1,13 +1,15 @@
1
1
  {
2
2
  "name": "@kontent-ai/mcp-server",
3
- "version": "0.12.0",
3
+ "version": "0.14.0",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "build": "rimraf build && tsc",
7
7
  "start:stdio": "node build/bin.js stdio",
8
8
  "start:sse": "node build/bin.js sse",
9
+ "start:shttp": "node build/bin.js shttp",
9
10
  "dev:stdio": "tsx watch src/bin.ts stdio",
10
11
  "dev:sse": "tsx watch src/bin.ts sse",
12
+ "dev:shttp": "tsx watch src/bin.ts shttp",
11
13
  "format": "cross-env node node_modules/@biomejs/biome/bin/biome ci ./ --config-path=./biome.json",
12
14
  "format:fix": "cross-env node node_modules/@biomejs/biome/bin/biome check ./ --fix --unsafe --config-path=./biome.json"
13
15
  },