@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 +79 -0
- package/build/clients/kontentClients.js +16 -0
- package/build/schemas/contentTypeSchemas.js +283 -0
- package/build/server.js +34 -0
- package/build/sseTransport.js +23 -0
- package/build/stdioTransport.js +13 -0
- package/build/tools/add-content-type-mapi.js +36 -0
- package/build/tools/add-content-type-snippet-mapi.js +36 -0
- package/build/tools/get-asset-mapi.js +21 -0
- package/build/tools/get-item-dapi.js +23 -0
- package/build/tools/get-item-mapi.js +21 -0
- package/build/tools/get-type-mapi.js +21 -0
- package/build/tools/get-variant-mapi.js +23 -0
- package/build/tools/list-assets-mapi.js +17 -0
- package/build/tools/list-content-types-mapi.js +17 -0
- package/build/tools/list-languages-mapi.js +17 -0
- package/package.json +33 -0
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
|
+
]);
|
package/build/server.js
ADDED
|
@@ -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
|
+
}
|