@kontent-ai/mcp-server 0.4.2 → 0.5.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
@@ -8,34 +8,116 @@
8
8
  [![MIT License][license-shield]][license-url]
9
9
  [![Discord][discord-shield]][discord-url]
10
10
 
11
- 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.
11
+ > Transform your content operations with AI-powered tools for Kontent.ai. Create, manage, and explore your structured content through natural language conversations in your favorite AI-enabled editor.
12
12
 
13
- ## Features
13
+ Kontent.ai MCP Server implements the Model Context Protocol to connect your Kontent.ai projects with AI tools like Claude, Cursor, and VS Code. It enables AI models to understand your content structure and perform operations through natural language instructions.
14
14
 
15
- - Retrieve content items, variants, and assets
16
- - List available languages and assets
17
- - Get, List, and Create content types and snippets
18
- - Get, List, and Create taxonomies
15
+ ## Key Features
19
16
 
20
- ## Getting Started
17
+ * 🚀 **Rapid prototyping**: Transform your diagrams into live content models in seconds
18
+ * 📈 **Data Visualisation**: Visualise your content model in any format you want
21
19
 
22
- ### Prerequisites
20
+ ## Table of Contents
23
21
 
24
- - Node.js (version specified in `.nvmrc`)
25
- - Kontent.ai account with API keys
22
+ * [🔌 Quickstart](#-quickstart)
23
+ * [🔑 Prerequisites](#-prerequisites)
24
+ * [🛠 Setup Options](#-setup-options)
25
+ * [🛠️ Available Tools](#️-available-tools)
26
+ * [⚙️ Configuration](#️-configuration)
27
+ * [🚀 Transport Options](#-transport-options)
28
+ * [📟 STDIO Transport](#-stdio-transport)
29
+ * [🌐 SSE Transport](#-sse-transport)
30
+ * [💻 Development](#-development)
31
+ * [🛠 Local Installation](#-local-installation)
32
+ * [📂 Project Structure](#-project-structure)
33
+ * [🔍 Debugging](#-debugging)
34
+ * [License](#license)
26
35
 
27
- ### Running
36
+ ## 🔌 Quickstart
28
37
 
29
- You can run this server with either stdio or sse transport.
38
+ ### 🔑 Prerequisites
30
39
 
31
- ### Stdio transport
40
+ Before you can use the MCP server, you need:
41
+
42
+ 1. **A Kontent.ai account** - [Sign up](https://kontent.ai/signup) if you don't have an account.
43
+ 1. **A project** - [Create a project](https://kontent.ai/learn/docs/projects#a-create-projects) to work with.
44
+ 1. **Management API key** - [Create a Management API key](https://kontent.ai/learn/docs/apis/api-keys#a-create-management-api-keys) with appropriate permissions.
45
+ 1. **Environment ID** - [Get your environment ID](https://kontent.ai/learn/docs/environments#a-get-your-environment-id).
46
+
47
+ ### 🛠 Setup Options
48
+
49
+ You can run the Kontent.ai MCP Server with npx:
50
+
51
+ #### STDIO Transport
52
+
53
+ ```bash
54
+ npx @kontent-ai/mcp-server@latest stdio
55
+ ```
56
+
57
+ #### SSE Transport
58
+
59
+ ```bash
60
+ npx @kontent-ai/mcp-server@latest sse
61
+ ```
62
+
63
+ ## 🛠️ Available Tools
64
+
65
+ ### Content Type Management
66
+
67
+ * **get-type-mapi** – Get a specific content type by codename
68
+ * **list-content-types-mapi** – List all content types in the environment
69
+ * **add-content-type-mapi** – Create a new content type with elements
70
+
71
+ ### Content Type Snippet Management
72
+
73
+ * **get-type-snippet-mapi** – Get a specific content type snippet by codename
74
+ * **list-content-type-snippets-mapi** – List all content type snippets
75
+ * **add-content-type-snippet-mapi** – Create a new content type snippet
76
+
77
+ ### Taxonomy Management
78
+
79
+ * **get-taxonomy-group-mapi** – Get a specific taxonomy group by codename
80
+ * **list-taxonomy-groups-mapi** – List all taxonomy groups
81
+ * **add-taxonomy-group-mapi** – Create a new taxonomy group with terms
82
+
83
+ ### Content Item Management
84
+
85
+ * **get-item-mapi** – Get a specific content item by codename
86
+ * **get-item-dapi** – Get a content item by codename from Delivery API
87
+ * **get-variant-mapi** – Get a language variant of a content item
88
+ * **add-content-item-mapi** – Create a new content item (structure only)
89
+ * **upsert-language-variant-mapi** – Create or update a language variant with content
90
+
91
+ ### Asset Management
92
+
93
+ * **get-asset-mapi** – Get a specific asset by codename
94
+ * **list-assets-mapi** – List all assets in the environment
95
+
96
+ ### Language Management
97
+
98
+ * **list-languages-mapi** – List all languages configured in the environment
99
+
100
+ ## ⚙️ Configuration
101
+
102
+ The server requires the following environment variables:
103
+
104
+ | Variable | Description | Required |
105
+ |----------|-------------|----------|
106
+ | KONTENT_API_KEY | Your Kontent.ai Management API key | ✅ |
107
+ | KONTENT_ENVIRONMENT_ID | Your environment ID | ✅ |
108
+ | PORT | Port for SSE transport (defaults to 3001) | ❌ |
109
+
110
+ ## 🚀 Transport Options
111
+
112
+ ### 📟 STDIO Transport
113
+
114
+ To run the server with STDIO transport, configure your MCP client with:
32
115
 
33
- To run the server with stdio transport configure your MCP client with the command to run and the necessary environment variables.
34
- Example:
35
116
  ```json
36
117
  {
37
118
  "kontent-ai-stdio": {
38
- "command": "npx @kontent-ai/mcp-server@latest stdio",
119
+ "command": "npx",
120
+ "args": ["@kontent-ai/mcp-server@latest", "stdio"],
39
121
  "env": {
40
122
  "KONTENT_API_KEY": "<management-api-key>",
41
123
  "KONTENT_ENVIRONMENT_ID": "<environment-id>"
@@ -44,22 +126,22 @@ Example:
44
126
  }
45
127
  ```
46
128
 
47
- ### SSE transport
129
+ ### 🌐 SSE Transport
48
130
 
49
- You can also run your server manually with the SSE transport and configure your MCP client to connect to the port the server is running on.
50
- Run the following command to start the server and ensure the environment variables are defined for it either by providing `.env` file in the `cwd` or providing the variables to the process any other way.
131
+ For SSE transport, first start the server:
51
132
 
52
133
  ```bash
53
134
  npx @kontent-ai/mcp-server@latest sse
54
135
  ```
136
+
137
+ With environment variables in a `.env` file, or otherwise accessible to the process:
55
138
  ```env
56
139
  KONTENT_API_KEY=<management-api-key>
57
140
  KONTENT_ENVIRONMENT_ID=<environment-id>
58
- PORT=<port-number> # optionally specify port, defaults to 3001
141
+ PORT=3001 # optional, defaults to 3001
59
142
  ```
60
143
 
61
- Then configure your MCP client to connect to the running server.
62
- Example:
144
+ Then configure your MCP client:
63
145
  ```json
64
146
  {
65
147
  "kontent-ai-sse": {
@@ -68,10 +150,9 @@ Example:
68
150
  }
69
151
  ```
70
152
 
153
+ ## 💻 Development
71
154
 
72
- ## Development
73
-
74
- ### Local Installation
155
+ ### 🛠 Local Installation
75
156
 
76
157
  ```bash
77
158
  # Clone the repository
@@ -89,21 +170,34 @@ npm run start:sse # For SSE transport
89
170
  npm run start:stdio # For STDIO transport
90
171
  ```
91
172
 
92
- ### Available Scripts
93
-
94
- - `npm run build` - Compile TypeScript to JavaScript
95
- - `npm run start:sse` - Start the server with Server-Sent Events transport
96
- - `npm run start:stdio` - Start the server with Standard IO transport
97
-
98
- ### Project Structure
173
+ ### 📂 Project Structure
99
174
 
100
175
  - `src/` - Source code
101
176
  - `tools/` - MCP tool implementations
102
177
  - `clients/` - Kontent.ai API client setup
103
178
  - `schemas/` - Data validation schemas
179
+ - `utils/` - Utility functions
180
+ - `errorHandler.ts` - Standardized error handling for MCP tools
181
+ - `throwError.ts` - Generic error throwing utility
104
182
  - `server.ts` - Main server setup and tool registration
105
183
  - `bin.ts` - Single entry point that handles both transport types
106
184
 
185
+ ### 🔍 Debugging
186
+
187
+ For debugging, you can use the MCP inspector:
188
+
189
+ ```bash
190
+ npx @modelcontextprotocol/inspector -e KONTENT_API_KEY=<key> -e KONTENT_ENVIRONMENT_ID=<env-id> node path/to/build/bin.js
191
+ ```
192
+
193
+ Or use the MCP inspector on a running sse server:
194
+
195
+ ```bash
196
+ npx @modelcontextprotocol/inspector
197
+ ```
198
+
199
+ This provides a web interface for inspecting and testing the available tools.
200
+
107
201
  ## License
108
202
 
109
203
  MIT
package/build/bin.js CHANGED
@@ -1,14 +1,16 @@
1
1
  #!/usr/bin/env node
2
- import 'dotenv/config';
2
+ import "dotenv/config";
3
3
  import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
4
4
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
5
  import express from "express";
6
+ import packageJson from "../package.json" with { type: "json" };
6
7
  import { createServer } from "./server.js";
8
+ const version = packageJson.version;
7
9
  async function startSSE() {
8
10
  const app = express();
9
11
  const { server } = createServer();
10
12
  let transport;
11
- app.get("/sse", async (req, res) => {
13
+ app.get("/sse", async (_req, res) => {
12
14
  transport = new SSEServerTransport("/message", res);
13
15
  await server.connect(transport);
14
16
  });
@@ -17,25 +19,27 @@ async function startSSE() {
17
19
  });
18
20
  const PORT = process.env.PORT || 3001;
19
21
  app.listen(PORT, () => {
20
- console.log(`Kontent.ai MCP Server (SSE) running on port ${PORT}`);
22
+ console.log(`Kontent.ai MCP Server v${version} (SSE) running on port ${PORT}`);
21
23
  });
22
24
  }
23
25
  async function startStdio() {
24
26
  const { server } = createServer();
25
27
  const transport = new StdioServerTransport();
28
+ console.log(`Kontent.ai MCP Server v${version} (stdio) starting`);
26
29
  await server.connect(transport);
27
30
  }
28
31
  async function main() {
29
32
  const args = process.argv.slice(2);
30
33
  const transportType = args[0]?.toLowerCase();
31
- if (!transportType || (transportType !== 'stdio' && transportType !== 'sse')) {
32
- console.error('Please specify a valid transport type: stdio or sse');
34
+ if (!transportType ||
35
+ (transportType !== "stdio" && transportType !== "sse")) {
36
+ console.error("Please specify a valid transport type: stdio or sse");
33
37
  process.exit(1);
34
38
  }
35
- if (transportType === 'stdio') {
39
+ if (transportType === "stdio") {
36
40
  await startStdio();
37
41
  }
38
- else if (transportType === 'sse') {
42
+ else if (transportType === "sse") {
39
43
  await startSSE();
40
44
  }
41
45
  }
@@ -1,5 +1,6 @@
1
- import { createManagementClient } from '@kontent-ai/management-sdk';
1
+ import { createManagementClient } from "@kontent-ai/management-sdk";
2
2
  import packageJson from "../../package.json" with { type: "json" };
3
+ import { throwError } from "../utils/throwError.js";
3
4
  const sourceTrackingHeaderName = "X-KC-SOURCE";
4
5
  /**
5
6
  * Creates a Kontent.ai Management API client
@@ -9,14 +10,17 @@ const sourceTrackingHeaderName = "X-KC-SOURCE";
9
10
  */
10
11
  export const createMapiClient = (environmentId, apiKey) => {
11
12
  return createManagementClient({
12
- apiKey: apiKey ?? process.env.KONTENT_API_KEY ?? throwError("KONTENT_API_KEY is not set"),
13
- environmentId: environmentId ?? process.env.KONTENT_ENVIRONMENT_ID ?? throwError("KONTENT_ENVIRONMENT_ID is not set"),
14
- headers: [{
13
+ apiKey: apiKey ??
14
+ process.env.KONTENT_API_KEY ??
15
+ throwError("KONTENT_API_KEY is not set"),
16
+ environmentId: environmentId ??
17
+ process.env.KONTENT_ENVIRONMENT_ID ??
18
+ throwError("KONTENT_ENVIRONMENT_ID is not set"),
19
+ headers: [
20
+ {
15
21
  header: sourceTrackingHeaderName,
16
22
  value: `${packageJson.name};${packageJson.version}`,
17
- }],
23
+ },
24
+ ],
18
25
  });
19
26
  };
20
- const throwError = (message) => {
21
- throw new Error(message);
22
- };
@@ -0,0 +1,205 @@
1
+ import { z } from "zod";
2
+ // Define a reusable reference object schema
3
+ const referenceObjectSchema = z
4
+ .object({
5
+ id: z.string().optional(),
6
+ codename: z.string().optional(),
7
+ external_id: z.string().optional(),
8
+ })
9
+ .describe("An object with an id, codename, or external_id property referencing another item. Using codename is preferred for better readability.");
10
+ // Language variant element value schemas
11
+ const textElementValueSchema = z.object({
12
+ value: z.string().describe("The text content of the element"),
13
+ });
14
+ const numberElementValueSchema = z.object({
15
+ value: z.number().describe("The numeric value of the element"),
16
+ });
17
+ const dateTimeElementValueSchema = z.object({
18
+ value: z
19
+ .string()
20
+ .nullable()
21
+ .describe("The ISO-8601 formatted date-time string, or null for empty"),
22
+ });
23
+ const multipleChoiceElementValueSchema = z.object({
24
+ value: z
25
+ .array(referenceObjectSchema)
26
+ .describe("Array of references to the selected options by their codename or id"),
27
+ });
28
+ const assetElementValueSchema = z.object({
29
+ value: z
30
+ .array(referenceObjectSchema)
31
+ .describe("Array of references to assets by their codename or id"),
32
+ });
33
+ const modularContentElementValueSchema = z.object({
34
+ value: z
35
+ .array(referenceObjectSchema)
36
+ .describe("Array of references to linked content items by their codename or id"),
37
+ });
38
+ const taxonomyElementValueSchema = z.object({
39
+ value: z
40
+ .array(referenceObjectSchema)
41
+ .describe("Array of references to taxonomy terms by their codename or id"),
42
+ });
43
+ const richTextElementValueSchema = z.object({
44
+ value: z.string().describe("The rich text content as HTML string"),
45
+ });
46
+ const urlSlugElementValueSchema = z.object({
47
+ value: z.string().describe("The URL slug value"),
48
+ });
49
+ const customElementValueSchema = z.object({
50
+ value: z.string().describe("The JSON string value for custom element"),
51
+ });
52
+ // Union type for all possible element values
53
+ const _elementValueSchema = z.discriminatedUnion("value", [
54
+ textElementValueSchema,
55
+ numberElementValueSchema,
56
+ dateTimeElementValueSchema,
57
+ multipleChoiceElementValueSchema,
58
+ assetElementValueSchema,
59
+ modularContentElementValueSchema,
60
+ taxonomyElementValueSchema,
61
+ richTextElementValueSchema,
62
+ urlSlugElementValueSchema,
63
+ customElementValueSchema,
64
+ ]);
65
+ // Language variant element schema
66
+ const languageVariantElementSchema = z
67
+ .record(z.string().describe("Element codename as key"), z
68
+ .union([
69
+ textElementValueSchema,
70
+ numberElementValueSchema,
71
+ dateTimeElementValueSchema,
72
+ multipleChoiceElementValueSchema,
73
+ assetElementValueSchema,
74
+ modularContentElementValueSchema,
75
+ taxonomyElementValueSchema,
76
+ richTextElementValueSchema,
77
+ urlSlugElementValueSchema,
78
+ customElementValueSchema,
79
+ ])
80
+ .describe("Element value object with 'value' property containing the element's content"))
81
+ .describe("Object where keys are element codenames and values are element value objects");
82
+ // Collection reference schema
83
+ const collectionReferenceSchema = z
84
+ .object({
85
+ reference: referenceObjectSchema
86
+ .nullable()
87
+ .describe("Reference to a collection by id, codename, or external_id. Use null to remove from collection."),
88
+ })
89
+ .describe("Collection assignment for the content item");
90
+ // Content item creation schema
91
+ export const contentItemCreateSchema = z
92
+ .object({
93
+ name: z
94
+ .string()
95
+ .min(1)
96
+ .max(200)
97
+ .describe("Display name of the content item (1-200 characters)"),
98
+ codename: z
99
+ .string()
100
+ .optional()
101
+ .describe("Codename of the content item (optional, will be generated from name if not provided)"),
102
+ type: referenceObjectSchema.describe("Reference to the content type by id, codename, or external_id"),
103
+ external_id: z
104
+ .string()
105
+ .optional()
106
+ .describe("External ID for the content item (optional, useful for external system integration)"),
107
+ collection: collectionReferenceSchema
108
+ .optional()
109
+ .describe("Collection assignment for the content item (optional)"),
110
+ })
111
+ .describe("Schema for creating a new content item");
112
+ // Language variant creation/update schema
113
+ export const languageVariantUpsertSchema = z
114
+ .object({
115
+ elements: languageVariantElementSchema.describe("Object containing element values for the language variant. Keys are element codenames, values are element value objects."),
116
+ workflow_step: referenceObjectSchema
117
+ .optional()
118
+ .describe("Reference to workflow step by id or codename (optional)"),
119
+ })
120
+ .describe("Schema for creating or updating a language variant of a content item");
121
+ // Complete content item with language variant schema for convenience
122
+ export const contentItemWithVariantSchema = z
123
+ .object({
124
+ item: contentItemCreateSchema.describe("Content item data"),
125
+ language_codename: z
126
+ .string()
127
+ .default("default")
128
+ .describe("Codename of the language for the variant (defaults to 'default')"),
129
+ variant: languageVariantUpsertSchema.describe("Language variant data with element values"),
130
+ })
131
+ .describe("Schema for creating a content item along with its language variant in one operation");
132
+ // Element value helper schemas for better LLM understanding
133
+ export const elementValueHelpers = {
134
+ text: z
135
+ .object({
136
+ value: z.string().describe("Text content"),
137
+ })
138
+ .describe("Text element value - use for simple text fields like titles, descriptions, etc."),
139
+ richText: z
140
+ .object({
141
+ value: z.string().describe("HTML content as string"),
142
+ })
143
+ .describe("Rich text element value - use for formatted content with HTML tags"),
144
+ number: z
145
+ .object({
146
+ value: z.number().describe("Numeric value"),
147
+ })
148
+ .describe("Number element value - use for numeric fields like prices, quantities, etc."),
149
+ dateTime: z
150
+ .object({
151
+ value: z
152
+ .string()
153
+ .nullable()
154
+ .describe("ISO-8601 date-time string or null"),
155
+ })
156
+ .describe("Date-time element value - use ISO format like '2023-12-25T10:30:00.000Z' or null for empty"),
157
+ multipleChoice: z
158
+ .object({
159
+ value: z
160
+ .array(z.object({
161
+ codename: z.string().describe("Codename of the selected option"),
162
+ }))
163
+ .describe("Array of selected option references"),
164
+ })
165
+ .describe("Multiple choice element value - reference options by their codename"),
166
+ asset: z
167
+ .object({
168
+ value: z
169
+ .array(z.object({
170
+ codename: z.string().describe("Codename of the asset"),
171
+ }))
172
+ .describe("Array of asset references"),
173
+ })
174
+ .describe("Asset element value - reference assets by their codename"),
175
+ modularContent: z
176
+ .object({
177
+ value: z
178
+ .array(z.object({
179
+ codename: z
180
+ .string()
181
+ .describe("Codename of the linked content item"),
182
+ }))
183
+ .describe("Array of content item references"),
184
+ })
185
+ .describe("Modular content element value - reference other content items by their codename"),
186
+ taxonomy: z
187
+ .object({
188
+ value: z
189
+ .array(z.object({
190
+ codename: z.string().describe("Codename of the taxonomy term"),
191
+ }))
192
+ .describe("Array of taxonomy term references"),
193
+ })
194
+ .describe("Taxonomy element value - reference taxonomy terms by their codename"),
195
+ urlSlug: z
196
+ .object({
197
+ value: z.string().describe("URL slug value"),
198
+ })
199
+ .describe("URL slug element value - use for SEO-friendly URLs"),
200
+ custom: z
201
+ .object({
202
+ value: z.string().describe("JSON string value"),
203
+ })
204
+ .describe("Custom element value - depends on the custom element implementation"),
205
+ };