@kontent-ai/mcp-server 0.1.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 ADDED
@@ -0,0 +1,79 @@
1
+ # Kontent.ai MCP Server
2
+
3
+ This server provides a Model Context Protocol (MCP) interface for interacting with Kontent.ai's Management and Delivery APIs. It enables AI assistants to access and manipulate Kontent.ai content using standardized tools.
4
+
5
+ ## Features
6
+
7
+ - Retrieve content items, variants, and assets
8
+ - Get content types and their structure
9
+ - List available languages and assets
10
+ - Create new content types and snippets
11
+
12
+ ## Getting Started
13
+
14
+ ### Prerequisites
15
+
16
+ - Node.js (version specified in `.nvmrc`)
17
+ - Kontent.ai account with API keys
18
+
19
+ ### Environment Variables
20
+
21
+ Create a `.env` file with the following variables:
22
+
23
+ ```
24
+ KONTENT_ENVIRONMENT_ID=your_environment_id
25
+ KONTENT_API_KEY=your_api_key
26
+ PORT=3001 # Optional, defaults to 3001
27
+ ```
28
+
29
+ ### Using with npx
30
+
31
+ You can run the server directly with npx:
32
+
33
+ ```bash
34
+ # Run with SSE transport
35
+ npx @kontent-ai/mcp-server sse
36
+
37
+ # Run with STDIO transport
38
+ npx @kontent-ai/mcp-server stdio
39
+ ```
40
+
41
+ ### Local Installation
42
+
43
+ ```bash
44
+ # Clone the repository
45
+ git clone https://github.com/kontent-ai/mcp-server.git
46
+ cd mcp-server
47
+
48
+ # Install dependencies
49
+ npm ci
50
+
51
+ # Build the project
52
+ npm run build
53
+
54
+ # Start the server
55
+ npm run start:sse # For SSE transport
56
+ npm run start:stdio # For STDIO transport
57
+ ```
58
+
59
+ ## Development
60
+
61
+ ### Available Scripts
62
+
63
+ - `npm run build` - Compile TypeScript to JavaScript
64
+ - `npm run start:sse` - Start the server with Server-Sent Events transport
65
+ - `npm run start:stdio` - Start the server with Standard IO transport
66
+
67
+ ### Project Structure
68
+
69
+ - `src/` - Source code
70
+ - `tools/` - MCP tool implementations
71
+ - `clients/` - Kontent.ai API client setup
72
+ - `schemas/` - Data validation schemas
73
+ - `server.ts` - Main server setup and tool registration
74
+ - `sseTransport.ts` - SSE transport implementation
75
+ - `stdioTransport.ts` - STDIO transport implementation
76
+
77
+ ## License
78
+
79
+ MIT
@@ -0,0 +1,16 @@
1
+ import { createManagementClient } from '@kontent-ai/management-sdk';
2
+ /**
3
+ * Creates a Kontent.ai Management API client
4
+ * @param environmentId Optional environment ID (defaults to process.env.KONTENT_ENVIRONMENT_ID)
5
+ * @param apiKey Optional API key (defaults to process.env.KONTENT_API_KEY)
6
+ * @returns Management API client instance
7
+ */
8
+ export const createMapiClient = (environmentId, apiKey) => {
9
+ return createManagementClient({
10
+ apiKey: apiKey ?? process.env.KONTENT_API_KEY ?? throwError("KONTENT_API_KEY is not set"),
11
+ environmentId: environmentId ?? process.env.KONTENT_ENVIRONMENT_ID ?? throwError("KONTENT_ENVIRONMENT_ID is not set"),
12
+ });
13
+ };
14
+ const throwError = (message) => {
15
+ throw new Error(message);
16
+ };
@@ -0,0 +1,283 @@
1
+ import { z } from "zod";
2
+ // Define a reusable reference object schema
3
+ const referenceObjectSchema = z.object({
4
+ id: z.string().optional(),
5
+ codename: z.string().optional(),
6
+ });
7
+ // Common property schemas
8
+ const baseElementSchema = {
9
+ codename: z.string().optional(),
10
+ external_id: z.string().optional(),
11
+ content_group: referenceObjectSchema.optional(),
12
+ };
13
+ const namedElementSchema = {
14
+ ...baseElementSchema,
15
+ name: z.string(),
16
+ guidelines: z.string().optional(),
17
+ is_required: z.boolean().optional(),
18
+ is_non_localizable: z.boolean().optional(),
19
+ };
20
+ // Limit schemas
21
+ const conditionEnum = z.enum(['at_most', 'exactly', 'at_least']);
22
+ const countLimitSchema = z.object({
23
+ value: z.number(),
24
+ condition: conditionEnum
25
+ }).optional();
26
+ const imageLimitSchema = {
27
+ image_width_limit: z.object({
28
+ value: z.number(),
29
+ condition: conditionEnum
30
+ }).optional(),
31
+ image_height_limit: z.object({
32
+ value: z.number(),
33
+ condition: conditionEnum
34
+ }).optional(),
35
+ };
36
+ // Default value schemas
37
+ const arrayDefaultSchema = (schema = referenceObjectSchema) => z.object({
38
+ global: z.object({
39
+ value: z.array(schema)
40
+ })
41
+ }).optional();
42
+ const stringDefaultSchema = z.object({
43
+ global: z.object({
44
+ value: z.string()
45
+ })
46
+ }).optional();
47
+ const numberDefaultSchema = z.object({
48
+ global: z.object({
49
+ value: z.number()
50
+ })
51
+ }).optional();
52
+ // Regex validation schema
53
+ const regexValidationSchema = z.object({
54
+ is_active: z.boolean(),
55
+ regex: z.string(),
56
+ flags: z.string().optional(),
57
+ validation_message: z.string().optional()
58
+ }).optional();
59
+ // Text length limit schema
60
+ const textLengthLimitSchema = z.object({
61
+ value: z.number(),
62
+ applies_to: z.enum(['words', 'characters'])
63
+ }).optional();
64
+ // Define a union type of all possible element types for content types
65
+ export const elementSchema = z.union([
66
+ // Asset element
67
+ z.object({
68
+ type: z.literal('asset'),
69
+ ...namedElementSchema,
70
+ asset_count_limit: countLimitSchema,
71
+ maximum_file_size: z.number().optional(),
72
+ allowed_file_types: z.enum(['adjustable', 'any']).optional(),
73
+ ...imageLimitSchema,
74
+ default: arrayDefaultSchema(),
75
+ }),
76
+ // Custom element
77
+ z.object({
78
+ type: z.literal('custom'),
79
+ ...namedElementSchema,
80
+ source_url: z.string(),
81
+ json_parameters: z.string().optional(),
82
+ allowed_elements: z.array(referenceObjectSchema).optional(),
83
+ }),
84
+ // Date time element
85
+ z.object({
86
+ type: z.literal('date_time'),
87
+ ...namedElementSchema,
88
+ default: stringDefaultSchema,
89
+ }),
90
+ // Guidelines element
91
+ z.object({
92
+ type: z.literal('guidelines'),
93
+ guidelines: z.string(),
94
+ ...baseElementSchema,
95
+ }),
96
+ // Linked items (modular content) element
97
+ z.object({
98
+ type: z.literal('modular_content'),
99
+ ...namedElementSchema,
100
+ allowed_content_types: z.array(referenceObjectSchema).optional(),
101
+ item_count_limit: countLimitSchema,
102
+ default: arrayDefaultSchema(),
103
+ }),
104
+ // Subpages element
105
+ z.object({
106
+ type: z.literal('subpages'),
107
+ ...namedElementSchema,
108
+ allowed_content_types: z.array(referenceObjectSchema).optional(),
109
+ item_count_limit: countLimitSchema,
110
+ }),
111
+ // Multiple choice element
112
+ z.object({
113
+ type: z.literal('multiple_choice'),
114
+ ...namedElementSchema,
115
+ mode: z.enum(['single', 'multiple']),
116
+ options: z.array(z.object({
117
+ name: z.string(),
118
+ codename: z.string().optional(),
119
+ external_id: z.string().optional(),
120
+ })),
121
+ default: arrayDefaultSchema(),
122
+ }),
123
+ // Number element
124
+ z.object({
125
+ type: z.literal('number'),
126
+ ...namedElementSchema,
127
+ default: numberDefaultSchema,
128
+ }),
129
+ // Rich text element
130
+ z.object({
131
+ type: z.literal('rich_text'),
132
+ ...namedElementSchema,
133
+ allowed_blocks: z.array(z.enum(['images', 'text', 'tables', 'components-and-items'])).optional(),
134
+ allowed_formatting: z.array(z.enum(['unstyled', 'bold', 'italic', 'code', 'link', 'subscript', 'superscript'])).optional(),
135
+ allowed_text_blocks: z.array(z.enum(['paragraph', 'heading-one', 'heading-two', 'heading-three', 'heading-four', 'heading-five', 'heading-six', 'ordered-list', 'unordered-list'])).optional(),
136
+ allowed_table_blocks: z.array(z.enum(['images', 'text'])).optional(),
137
+ allowed_table_formatting: z.array(z.enum(['unstyled', 'bold', 'italic', 'code', 'link', 'subscript', 'superscript'])).optional(),
138
+ allowed_table_text_blocks: z.array(z.enum(['paragraph', 'heading-one', 'heading-two', 'heading-three', 'heading-four', 'heading-five', 'heading-six', 'ordered-list', 'unordered-list'])).optional(),
139
+ allowed_content_types: z.array(referenceObjectSchema).optional(),
140
+ allowed_item_link_types: z.array(referenceObjectSchema).optional(),
141
+ ...imageLimitSchema,
142
+ allowed_image_types: z.enum(['adjustable', 'any']).optional(),
143
+ maximum_image_size: z.number().optional(),
144
+ maximum_text_length: textLengthLimitSchema,
145
+ }),
146
+ // Snippet element
147
+ z.object({
148
+ type: z.literal('snippet'),
149
+ snippet: referenceObjectSchema,
150
+ ...baseElementSchema,
151
+ }),
152
+ // Taxonomy element
153
+ z.object({
154
+ type: z.literal('taxonomy'),
155
+ taxonomy_group: referenceObjectSchema,
156
+ ...namedElementSchema,
157
+ term_count_limit: countLimitSchema,
158
+ default: arrayDefaultSchema(),
159
+ }),
160
+ // Text element
161
+ z.object({
162
+ type: z.literal('text'),
163
+ ...namedElementSchema,
164
+ maximum_text_length: textLengthLimitSchema,
165
+ validation_regex: regexValidationSchema,
166
+ default: stringDefaultSchema,
167
+ }),
168
+ // URL slug element
169
+ z.object({
170
+ type: z.literal('url_slug'),
171
+ ...namedElementSchema,
172
+ depends_on: z.object({
173
+ element: referenceObjectSchema,
174
+ snippet: referenceObjectSchema.optional(),
175
+ }),
176
+ validation_regex: regexValidationSchema,
177
+ }),
178
+ ]);
179
+ // Define schema for content groups
180
+ export const contentGroupSchema = z.object({
181
+ name: z.string(),
182
+ external_id: z.string().optional(),
183
+ codename: z.string().optional(),
184
+ });
185
+ // Define a union type for snippet elements (excluding url_slug and snippet elements)
186
+ export const snippetElementSchema = z.union([
187
+ // Asset element
188
+ z.object({
189
+ type: z.literal('asset'),
190
+ ...namedElementSchema,
191
+ asset_count_limit: countLimitSchema,
192
+ maximum_file_size: z.number().optional(),
193
+ allowed_file_types: z.enum(['adjustable', 'any']).optional(),
194
+ ...imageLimitSchema,
195
+ default: arrayDefaultSchema(),
196
+ }),
197
+ // Custom element
198
+ z.object({
199
+ type: z.literal('custom'),
200
+ ...namedElementSchema,
201
+ source_url: z.string(),
202
+ json_parameters: z.string().optional(),
203
+ allowed_elements: z.array(referenceObjectSchema).optional(),
204
+ }),
205
+ // Date time element
206
+ z.object({
207
+ type: z.literal('date_time'),
208
+ ...namedElementSchema,
209
+ default: stringDefaultSchema,
210
+ }),
211
+ // Guidelines element
212
+ z.object({
213
+ type: z.literal('guidelines'),
214
+ guidelines: z.string(),
215
+ ...baseElementSchema,
216
+ }),
217
+ // Linked items (modular content) element
218
+ z.object({
219
+ type: z.literal('modular_content'),
220
+ ...namedElementSchema,
221
+ allowed_content_types: z.array(referenceObjectSchema).optional(),
222
+ item_count_limit: countLimitSchema,
223
+ default: arrayDefaultSchema(),
224
+ }),
225
+ // Subpages element
226
+ z.object({
227
+ type: z.literal('subpages'),
228
+ ...namedElementSchema,
229
+ allowed_content_types: z.array(referenceObjectSchema).optional(),
230
+ item_count_limit: countLimitSchema,
231
+ }),
232
+ // Multiple choice element
233
+ z.object({
234
+ type: z.literal('multiple_choice'),
235
+ ...namedElementSchema,
236
+ mode: z.enum(['single', 'multiple']),
237
+ options: z.array(z.object({
238
+ name: z.string(),
239
+ codename: z.string().optional(),
240
+ external_id: z.string().optional(),
241
+ })),
242
+ default: arrayDefaultSchema(),
243
+ }),
244
+ // Number element
245
+ z.object({
246
+ type: z.literal('number'),
247
+ ...namedElementSchema,
248
+ default: numberDefaultSchema,
249
+ }),
250
+ // Rich text element
251
+ z.object({
252
+ type: z.literal('rich_text'),
253
+ ...namedElementSchema,
254
+ allowed_blocks: z.array(z.enum(['images', 'text', 'tables', 'components-and-items'])).optional(),
255
+ allowed_formatting: z.array(z.enum(['unstyled', 'bold', 'italic', 'code', 'link', 'subscript', 'superscript'])).optional(),
256
+ allowed_text_blocks: z.array(z.enum(['paragraph', 'heading-one', 'heading-two', 'heading-three', 'heading-four', 'heading-five', 'heading-six', 'ordered-list', 'unordered-list'])).optional(),
257
+ allowed_table_blocks: z.array(z.enum(['images', 'text'])).optional(),
258
+ allowed_table_formatting: z.array(z.enum(['unstyled', 'bold', 'italic', 'code', 'link', 'subscript', 'superscript'])).optional(),
259
+ allowed_table_text_blocks: z.array(z.enum(['paragraph', 'heading-one', 'heading-two', 'heading-three', 'heading-four', 'heading-five', 'heading-six', 'ordered-list', 'unordered-list'])).optional(),
260
+ allowed_content_types: z.array(referenceObjectSchema).optional(),
261
+ allowed_item_link_types: z.array(referenceObjectSchema).optional(),
262
+ ...imageLimitSchema,
263
+ allowed_image_types: z.enum(['adjustable', 'any']).optional(),
264
+ maximum_image_size: z.number().optional(),
265
+ maximum_text_length: textLengthLimitSchema,
266
+ }),
267
+ // Taxonomy element
268
+ z.object({
269
+ type: z.literal('taxonomy'),
270
+ taxonomy_group: referenceObjectSchema,
271
+ ...namedElementSchema,
272
+ term_count_limit: countLimitSchema,
273
+ default: arrayDefaultSchema(),
274
+ }),
275
+ // Text element
276
+ z.object({
277
+ type: z.literal('text'),
278
+ ...namedElementSchema,
279
+ maximum_text_length: textLengthLimitSchema,
280
+ validation_regex: regexValidationSchema,
281
+ default: stringDefaultSchema,
282
+ }),
283
+ ]);
@@ -0,0 +1,34 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { registerTool as registerGetItemMapi } from "./tools/get-item-mapi.js";
3
+ import { registerTool as registerGetItemDapi } from "./tools/get-item-dapi.js";
4
+ import { registerTool as registerGetVariantMapi } from "./tools/get-variant-mapi.js";
5
+ import { registerTool as registerGetTypeMapi } from "./tools/get-type-mapi.js";
6
+ import { registerTool as registerListContentTypesMapi } from "./tools/list-content-types-mapi.js";
7
+ import { registerTool as registerListLanguagesMapi } from "./tools/list-languages-mapi.js";
8
+ import { registerTool as registerGetAssetMapi } from "./tools/get-asset-mapi.js";
9
+ import { registerTool as registerListAssetsMapi } from "./tools/list-assets-mapi.js";
10
+ import { registerTool as registerAddContentTypeMapi } from "./tools/add-content-type-mapi.js";
11
+ import { registerTool as registerAddContentTypeSnippetMapi } from "./tools/add-content-type-snippet-mapi.js";
12
+ // Create server instance
13
+ export const createServer = () => {
14
+ const server = new McpServer({
15
+ name: "kontent-ai",
16
+ version: "1.0.0",
17
+ capabilities: {
18
+ resources: {},
19
+ tools: {},
20
+ },
21
+ });
22
+ // Register all tools
23
+ registerGetItemMapi(server);
24
+ registerGetItemDapi(server);
25
+ registerGetVariantMapi(server);
26
+ registerGetTypeMapi(server);
27
+ registerListContentTypesMapi(server);
28
+ registerListLanguagesMapi(server);
29
+ registerGetAssetMapi(server);
30
+ registerListAssetsMapi(server);
31
+ registerAddContentTypeMapi(server);
32
+ registerAddContentTypeSnippetMapi(server);
33
+ return { server };
34
+ };
@@ -0,0 +1,23 @@
1
+ import 'dotenv/config';
2
+ import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
3
+ import express from "express";
4
+ import { createServer } from "./server.js";
5
+ const app = express();
6
+ const { server } = createServer();
7
+ let transport;
8
+ app.get("/sse", async (req, res) => {
9
+ transport = new SSEServerTransport("/message", res);
10
+ await server.connect(transport);
11
+ /*server.onclose = async () => {
12
+ //await cleanup();
13
+ await server.close();
14
+ process.exit(0);
15
+ };*/
16
+ });
17
+ app.post("/message", async (req, res) => {
18
+ await transport.handlePostMessage(req, res);
19
+ });
20
+ const PORT = process.env.PORT || 3001;
21
+ app.listen(PORT, () => {
22
+ console.log(`Kontent.ai MCP Server (SSE) running on port ${PORT}`);
23
+ });
@@ -0,0 +1,13 @@
1
+ import 'dotenv/config';
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { createServer } from "./server.js";
4
+ // Create server instance
5
+ const { server } = createServer();
6
+ async function main() {
7
+ const transport = new StdioServerTransport();
8
+ await server.connect(transport);
9
+ }
10
+ main().catch((error) => {
11
+ console.error("Fatal error in main():", error);
12
+ process.exit(1);
13
+ });
@@ -0,0 +1,36 @@
1
+ import { z } from "zod";
2
+ import { createMapiClient } from '../clients/kontentClients.js';
3
+ import { elementSchema, contentGroupSchema } from '../schemas/contentTypeSchemas.js';
4
+ export const registerTool = (server) => {
5
+ server.tool("add-content-type-mapi", "Add a new content type via Management API", {
6
+ name: z.string().describe("Display name of the content type"),
7
+ codename: z.string().optional().describe("Codename of the content type (optional, will be generated if not provided)"),
8
+ external_id: z.string().optional().describe("External ID of the content type (optional)"),
9
+ elements: z.array(elementSchema).describe("Array of elements that define the structure of the content type"),
10
+ content_groups: z.array(contentGroupSchema).optional().describe("Array of content groups (optional)"),
11
+ }, async ({ name, codename, external_id, elements, content_groups }) => {
12
+ const client = createMapiClient();
13
+ const response = await client
14
+ .addContentType()
15
+ .withData(() => ({
16
+ name,
17
+ codename,
18
+ external_id,
19
+ elements,
20
+ content_groups: content_groups?.map(group => ({
21
+ name: group.name,
22
+ external_id: group.external_id,
23
+ codename: group.codename
24
+ })),
25
+ }))
26
+ .toPromise();
27
+ return {
28
+ content: [
29
+ {
30
+ type: "text",
31
+ text: JSON.stringify(response.rawData),
32
+ },
33
+ ],
34
+ };
35
+ });
36
+ };
@@ -0,0 +1,36 @@
1
+ import { z } from "zod";
2
+ import { createMapiClient } from '../clients/kontentClients.js';
3
+ import { snippetElementSchema, contentGroupSchema } from '../schemas/contentTypeSchemas.js';
4
+ export const registerTool = (server) => {
5
+ server.tool("add-content-type-snippet-mapi", "Add a new content type snippet via Management API", {
6
+ name: z.string().describe("Display name of the content type snippet"),
7
+ codename: z.string().optional().describe("Codename of the content type snippet (optional, will be generated if not provided)"),
8
+ external_id: z.string().optional().describe("External ID of the content type snippet (optional)"),
9
+ elements: z.array(snippetElementSchema).describe("Array of elements that define the structure of the content type snippet"),
10
+ content_groups: z.array(contentGroupSchema).optional().describe("Array of content groups (optional)"),
11
+ }, async ({ name, codename, external_id, elements, content_groups }) => {
12
+ const client = createMapiClient();
13
+ const response = await client
14
+ .addContentTypeSnippet()
15
+ .withData(() => ({
16
+ name,
17
+ codename,
18
+ external_id,
19
+ elements,
20
+ content_groups: content_groups?.map(group => ({
21
+ name: group.name,
22
+ external_id: group.external_id,
23
+ codename: group.codename
24
+ })),
25
+ }))
26
+ .toPromise();
27
+ return {
28
+ content: [
29
+ {
30
+ type: "text",
31
+ text: JSON.stringify(response.rawData),
32
+ },
33
+ ],
34
+ };
35
+ });
36
+ };
@@ -0,0 +1,21 @@
1
+ import { z } from "zod";
2
+ import { createMapiClient } from '../clients/kontentClients.js';
3
+ export const registerTool = (server) => {
4
+ server.tool("get-asset-mapi", "Get a specific asset by codename from Management API", {
5
+ assetCodename: z.string().describe("Codename of the asset to retrieve")
6
+ }, async ({ assetCodename }) => {
7
+ const client = createMapiClient();
8
+ const response = await client
9
+ .viewAsset()
10
+ .byAssetCodename(assetCodename)
11
+ .toPromise();
12
+ return {
13
+ content: [
14
+ {
15
+ type: "text",
16
+ text: JSON.stringify(response.data),
17
+ },
18
+ ],
19
+ };
20
+ });
21
+ };
@@ -0,0 +1,23 @@
1
+ import { z } from "zod";
2
+ import { createDeliveryClient } from '@kontent-ai/delivery-sdk';
3
+ export const registerTool = (server) => {
4
+ server.tool("get-item-dapi", "Get Kontent.ai item by codename from Delivery API", {
5
+ codename: z.string().describe("Codename of the item to get"),
6
+ environmentId: z.string().describe("Environment ID of the item's environment"),
7
+ }, async ({ codename, environmentId }) => {
8
+ const client = createDeliveryClient({
9
+ environmentId,
10
+ });
11
+ const response = await client
12
+ .item(codename)
13
+ .toPromise();
14
+ return {
15
+ content: [
16
+ {
17
+ type: "text",
18
+ text: JSON.stringify(response.data.item),
19
+ },
20
+ ],
21
+ };
22
+ });
23
+ };
@@ -0,0 +1,21 @@
1
+ import { z } from "zod";
2
+ import { createMapiClient } from '../clients/kontentClients.js';
3
+ export const registerTool = (server) => {
4
+ server.tool("get-item-mapi", "Get Kontent.ai item by codename from Management API", {
5
+ codename: z.string().describe("Codename of the item to get")
6
+ }, async ({ codename }) => {
7
+ const client = createMapiClient();
8
+ const response = await client
9
+ .viewContentItem()
10
+ .byItemCodename(codename)
11
+ .toPromise();
12
+ return {
13
+ content: [
14
+ {
15
+ type: "text",
16
+ text: JSON.stringify(response.data),
17
+ },
18
+ ],
19
+ };
20
+ });
21
+ };
@@ -0,0 +1,21 @@
1
+ import { z } from "zod";
2
+ import { createMapiClient } from '../clients/kontentClients.js';
3
+ export const registerTool = (server) => {
4
+ server.tool("get-type-mapi", "Get content type by codename from Management API", {
5
+ codename: z.string().describe("Codename of the content type to get")
6
+ }, async ({ codename }) => {
7
+ const client = createMapiClient();
8
+ const response = await client
9
+ .viewContentType()
10
+ .byTypeCodename(codename)
11
+ .toPromise();
12
+ return {
13
+ content: [
14
+ {
15
+ type: "text",
16
+ text: JSON.stringify(response.data),
17
+ },
18
+ ],
19
+ };
20
+ });
21
+ };
@@ -0,0 +1,23 @@
1
+ import { z } from "zod";
2
+ import { createMapiClient } from '../clients/kontentClients.js';
3
+ export const registerTool = (server) => {
4
+ server.tool("get-variant-mapi", "Get language variant of a content item from Management API", {
5
+ itemCodename: z.string().describe("Codename of the content item"),
6
+ languageCodename: z.string().describe("Codename of the language variant to get")
7
+ }, async ({ itemCodename, languageCodename }) => {
8
+ const client = createMapiClient();
9
+ const response = await client
10
+ .viewLanguageVariant()
11
+ .byItemCodename(itemCodename)
12
+ .byLanguageCodename(languageCodename)
13
+ .toPromise();
14
+ return {
15
+ content: [
16
+ {
17
+ type: "text",
18
+ text: JSON.stringify(response.data),
19
+ },
20
+ ],
21
+ };
22
+ });
23
+ };
@@ -0,0 +1,17 @@
1
+ import { createMapiClient } from '../clients/kontentClients.js';
2
+ export const registerTool = (server) => {
3
+ server.tool("list-assets-mapi", "Get all assets from Management API", {}, async () => {
4
+ const client = createMapiClient();
5
+ const response = await client
6
+ .listAssets()
7
+ .toAllPromise();
8
+ return {
9
+ content: [
10
+ {
11
+ type: "text",
12
+ text: JSON.stringify(response.data),
13
+ },
14
+ ],
15
+ };
16
+ });
17
+ };
@@ -0,0 +1,17 @@
1
+ import { createMapiClient } from '../clients/kontentClients.js';
2
+ export const registerTool = (server) => {
3
+ server.tool("list-content-types-mapi", "Get all content types from Management API", {}, async () => {
4
+ const client = createMapiClient();
5
+ const response = await client
6
+ .listContentTypes()
7
+ .toAllPromise();
8
+ return {
9
+ content: [
10
+ {
11
+ type: "text",
12
+ text: JSON.stringify(response.data),
13
+ },
14
+ ],
15
+ };
16
+ });
17
+ };
@@ -0,0 +1,17 @@
1
+ import { createMapiClient } from '../clients/kontentClients.js';
2
+ export const registerTool = (server) => {
3
+ server.tool("list-languages-mapi", "Get all languages from Management API", {}, async () => {
4
+ const client = createMapiClient();
5
+ const response = await client
6
+ .listLanguages()
7
+ .toAllPromise();
8
+ return {
9
+ content: [
10
+ {
11
+ type: "text",
12
+ text: JSON.stringify(response.data),
13
+ },
14
+ ],
15
+ };
16
+ });
17
+ };
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@kontent-ai/mcp-server",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "scripts": {
6
+ "build": "tsc",
7
+ "start:stdio": "node build/stdioTransport.js",
8
+ "start:sse": "node build/sseTransport.js"
9
+ },
10
+ "bin": {
11
+ "stdio": "./build/stdioTransport.js",
12
+ "sse": "./build/sseTransport.js"
13
+ },
14
+ "files": [
15
+ "build"
16
+ ],
17
+ "keywords": [],
18
+ "author": "Jiri Lojda",
19
+ "license": "MIT",
20
+ "dependencies": {
21
+ "@kontent-ai/delivery-sdk": "^16.1.0",
22
+ "@kontent-ai/management-sdk": "^7.9.0",
23
+ "@modelcontextprotocol/sdk": "^1.9.0",
24
+ "dotenv": "^16.5.0",
25
+ "express": "^5.1.0",
26
+ "zod": "^3.24.2"
27
+ },
28
+ "devDependencies": {
29
+ "@types/express": "^5.0.1",
30
+ "@types/node": "^22.14.1",
31
+ "typescript": "^5.8.3"
32
+ }
33
+ }