@kontent-ai/mcp-server 0.16.0 → 0.18.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.
Files changed (38) hide show
  1. package/README.md +112 -24
  2. package/build/bin.js +81 -30
  3. package/build/clients/kontentClients.js +3 -1
  4. package/build/config/appConfiguration.js +51 -0
  5. package/build/server.js +29 -29
  6. package/build/tools/add-content-item-mapi.js +3 -3
  7. package/build/tools/add-content-type-mapi.js +3 -3
  8. package/build/tools/add-content-type-snippet-mapi.js +3 -3
  9. package/build/tools/add-taxonomy-group-mapi.js +3 -3
  10. package/build/tools/change-variant-workflow-step-mapi.js +3 -3
  11. package/build/tools/create-variant-version-mapi.js +3 -3
  12. package/build/tools/delete-content-item-mapi.js +3 -3
  13. package/build/tools/delete-content-type-mapi.js +3 -3
  14. package/build/tools/delete-language-variant-mapi.js +3 -3
  15. package/build/tools/filter-variants-mapi.js +13 -7
  16. package/build/tools/get-asset-mapi.js +3 -3
  17. package/build/tools/get-item-dapi.js +4 -1
  18. package/build/tools/get-item-mapi.js +3 -3
  19. package/build/tools/get-taxonomy-group-mapi.js +3 -3
  20. package/build/tools/get-type-mapi.js +3 -3
  21. package/build/tools/get-type-snippet-mapi.js +3 -3
  22. package/build/tools/get-variant-mapi.js +3 -3
  23. package/build/tools/list-assets-mapi.js +3 -3
  24. package/build/tools/list-content-type-snippets-mapi.js +3 -3
  25. package/build/tools/list-content-types-mapi.js +3 -3
  26. package/build/tools/list-languages-mapi.js +3 -3
  27. package/build/tools/list-taxonomy-groups-mapi.js +3 -3
  28. package/build/tools/list-workflows-mapi.js +3 -3
  29. package/build/tools/patch-content-type-mapi.js +3 -3
  30. package/build/tools/publish-variant-mapi.js +3 -3
  31. package/build/tools/unpublish-variant-mapi.js +3 -3
  32. package/build/tools/update-content-item-mapi.js +3 -3
  33. package/build/tools/upsert-language-variant-mapi.js +3 -3
  34. package/build/utils/errorHandler.js +2 -2
  35. package/build/utils/extractBearerToken.js +12 -0
  36. package/build/utils/isValidGuid.js +9 -0
  37. package/build/utils/responseHelper.js +1 -1
  38. package/package.json +5 -3
package/README.md CHANGED
@@ -48,10 +48,10 @@ You can run the Kontent.ai MCP Server with npx:
48
48
  npx @kontent-ai/mcp-server@latest stdio
49
49
  ```
50
50
 
51
- #### SSE Transport
51
+ #### Streamable HTTP Transport
52
52
 
53
53
  ```bash
54
- npx @kontent-ai/mcp-server@latest sse
54
+ npx @kontent-ai/mcp-server@latest shttp
55
55
  ```
56
56
 
57
57
  ## 🛠️ Available Tools
@@ -111,13 +111,25 @@ npx @kontent-ai/mcp-server@latest sse
111
111
 
112
112
  ## ⚙️ Configuration
113
113
 
114
- The server requires the following environment variables:
114
+ The server supports two configuration modes:
115
+
116
+ ### Single-Tenant Mode (Default)
117
+
118
+ For single-tenant mode, configure environment variables:
115
119
 
116
120
  | Variable | Description | Required |
117
121
  |----------|-------------|----------|
118
122
  | KONTENT_API_KEY | Your Kontent.ai Management API key | ✅ |
119
123
  | KONTENT_ENVIRONMENT_ID | Your environment ID | ✅ |
120
- | PORT | Port for SSE transport (defaults to 3001) | ❌ |
124
+ | PORT | Port for HTTP transport (defaults to 3001) | ❌ |
125
+
126
+ ### Multi-Tenant Mode
127
+
128
+ For multi-tenant mode (Streamable HTTP only), the server accepts:
129
+ - **Environment ID** as a URL path parameter: `/{environmentId}/mcp`
130
+ - **API Key** via Bearer token in the Authorization header: `Authorization: Bearer <api-key>`
131
+
132
+ This mode allows a single server instance to handle requests for multiple Kontent.ai environments securely without requiring environment variables.
121
133
 
122
134
  ## 🚀 Transport Options
123
135
 
@@ -138,14 +150,16 @@ To run the server with STDIO transport, configure your MCP client with:
138
150
  }
139
151
  ```
140
152
 
141
- ### 🌐 SSE Transport
153
+ ### 🌊 Streamable HTTP Transport
142
154
 
143
- For SSE transport, first start the server:
155
+ For Streamable HTTP transport, first start the server:
144
156
 
145
157
  ```bash
146
- npx @kontent-ai/mcp-server@latest sse
158
+ npx @kontent-ai/mcp-server@latest shttp
147
159
  ```
148
160
 
161
+ #### Single-Tenant Mode
162
+
149
163
  With environment variables in a `.env` file, or otherwise accessible to the process:
150
164
  ```env
151
165
  KONTENT_API_KEY=<management-api-key>
@@ -156,36 +170,112 @@ PORT=3001 # optional, defaults to 3001
156
170
  Then configure your MCP client:
157
171
  ```json
158
172
  {
159
- "kontent-ai-sse": {
160
- "url": "http://localhost:3001/sse"
173
+ "kontent-ai-http": {
174
+ "url": "http://localhost:3001/mcp"
161
175
  }
162
176
  }
163
177
  ```
164
178
 
165
- ### 🌊 Streamable HTTP Transport
179
+ #### Multi-Tenant Mode
166
180
 
167
- For Streamable HTTP transport, first start the server:
181
+ No environment variables required. The server accepts requests for multiple environments using URL path parameters and Bearer authentication.
168
182
 
169
- ```bash
170
- npx @kontent-ai/mcp-server@latest shttp
183
+ ##### VS Code Configuration
184
+
185
+ Create a `.vscode/mcp.json` file in your workspace:
186
+
187
+ ```json
188
+ {
189
+ "servers": {
190
+ "kontent-ai-multi": {
191
+ "uri": "http://localhost:3001/<environment-id>/mcp",
192
+ "headers": {
193
+ "Authorization": "Bearer <management-api-key>"
194
+ }
195
+ }
196
+ }
197
+ }
171
198
  ```
172
199
 
173
- With environment variables in a `.env` file, or otherwise accessible to the process:
174
- ```env
175
- KONTENT_API_KEY=<management-api-key>
176
- KONTENT_ENVIRONMENT_ID=<environment-id>
177
- PORT=3001 # optional, defaults to 3001
200
+ For secure configuration with input prompts:
201
+
202
+ ```json
203
+ {
204
+ "inputs": [
205
+ {
206
+ "id": "apiKey",
207
+ "type": "password",
208
+ "description": "Kontent.ai API Key"
209
+ },
210
+ {
211
+ "id": "environmentId",
212
+ "type": "text",
213
+ "description": "Environment ID"
214
+ }
215
+ ],
216
+ "servers": {
217
+ "kontent-ai-multi": {
218
+ "uri": "http://localhost:3001/${inputs.environmentId}/mcp",
219
+ "headers": {
220
+ "Authorization": "Bearer ${inputs.apiKey}"
221
+ }
222
+ }
223
+ }
224
+ }
178
225
  ```
179
226
 
180
- Then configure your MCP client:
227
+ ##### Claude Desktop Configuration
228
+
229
+ Update your Claude Desktop configuration file:
230
+ - **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
231
+ - **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
232
+ - **Linux**: `~/.config/Claude/claude_desktop_config.json`
233
+
234
+ Use `mcp-remote` as a proxy to add authentication headers:
235
+
181
236
  ```json
182
237
  {
183
- "kontent-ai-http": {
184
- "url": "http://localhost:3001/mcp"
238
+ "mcpServers": {
239
+ "kontent-ai-multi": {
240
+ "command": "npx",
241
+ "args": [
242
+ "mcp-remote",
243
+ "http://localhost:3001/<environment-id>/mcp",
244
+ "--header",
245
+ "Authorization: Bearer <management-api-key>"
246
+ ]
247
+ }
185
248
  }
186
249
  }
187
250
  ```
188
251
 
252
+ ##### Claude Code Configuration
253
+
254
+ For Claude Code (claude.ai/code), add the server configuration:
255
+
256
+ ```bash
257
+ # Add the multi-tenant server
258
+ claude mcp add \
259
+ --url "http://localhost:3001/<environment-id>/mcp" \
260
+ --header "Authorization: Bearer <management-api-key>" \
261
+ kontent-ai-multi
262
+ ```
263
+
264
+ Or configure directly in the settings:
265
+
266
+ ```json
267
+ {
268
+ "kontent-ai-multi": {
269
+ "url": "http://localhost:3001/<environment-id>/mcp",
270
+ "headers": {
271
+ "Authorization": "Bearer <management-api-key>"
272
+ }
273
+ }
274
+ }
275
+ ```
276
+
277
+ **Important**: Replace `<environment-id>` with your actual Kontent.ai environment ID (GUID format) and `<management-api-key>` with your Management API key.
278
+
189
279
  ## 💻 Development
190
280
 
191
281
  ### 🛠 Local Installation
@@ -202,12 +292,10 @@ npm ci
202
292
  npm run build
203
293
 
204
294
  # Start the server
205
- npm run start:sse # For SSE transport
206
295
  npm run start:stdio # For STDIO transport
207
296
  npm run start:shttp # For Streamable HTTP transport
208
297
 
209
298
  # Start the server with automatic reloading (no need to build first)
210
- npm run dev:sse # For SSE transport
211
299
  npm run dev:stdio # For STDIO transport
212
300
  npm run dev:shttp # For Streamable HTTP transport
213
301
  ```
@@ -232,7 +320,7 @@ For debugging, you can use the MCP inspector:
232
320
  npx @modelcontextprotocol/inspector -e KONTENT_API_KEY=<key> -e KONTENT_ENVIRONMENT_ID=<env-id> node path/to/build/bin.js
233
321
  ```
234
322
 
235
- Or use the MCP inspector on a running sse server:
323
+ Or use the MCP inspector on a running streamable HTTP server:
236
324
 
237
325
  ```bash
238
326
  npx @modelcontextprotocol/inspector
package/build/bin.js CHANGED
@@ -1,18 +1,20 @@
1
1
  #!/usr/bin/env node
2
- import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
3
2
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
3
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
5
4
  import "dotenv/config";
6
5
  import express from "express";
7
6
  import packageJson from "../package.json" with { type: "json" };
7
+ import { loadAppConfiguration, } from "./config/appConfiguration.js";
8
8
  import { createServer } from "./server.js";
9
+ import { extractBearerToken } from "./utils/extractBearerToken.js";
10
+ import { isValidGuid } from "./utils/isValidGuid.js";
9
11
  const version = packageJson.version;
10
- async function startStreamableHTTP() {
12
+ async function startStreamableHTTP(config) {
11
13
  const app = express();
12
14
  app.use(express.json());
13
15
  app.post("/mcp", async (req, res) => {
14
16
  try {
15
- const { server } = createServer();
17
+ const { server } = createServer(config);
16
18
  const transport = new StreamableHTTPServerTransport({
17
19
  sessionIdGenerator: undefined,
18
20
  });
@@ -39,7 +41,6 @@ async function startStreamableHTTP() {
39
41
  }
40
42
  });
41
43
  app.get("/mcp", async (_, res) => {
42
- console.log("Received GET MCP request");
43
44
  res.writeHead(405).end(JSON.stringify({
44
45
  jsonrpc: "2.0",
45
46
  error: {
@@ -50,7 +51,6 @@ async function startStreamableHTTP() {
50
51
  }));
51
52
  });
52
53
  app.delete("/mcp", async (_, res) => {
53
- console.log("Received DELETE MCP request");
54
54
  res.writeHead(405).end(JSON.stringify({
55
55
  jsonrpc: "2.0",
56
56
  error: {
@@ -60,51 +60,102 @@ async function startStreamableHTTP() {
60
60
  id: null,
61
61
  }));
62
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}`);
63
+ app.post("/:environmentId/mcp", async (req, res) => {
64
+ try {
65
+ const { environmentId } = req.params;
66
+ if (!isValidGuid(environmentId)) {
67
+ res.status(400).json({
68
+ error: "Invalid environment ID format. Must be a valid GUID.",
69
+ });
70
+ return;
71
+ }
72
+ const { server } = createServer(config);
73
+ const transport = new StreamableHTTPServerTransport({
74
+ sessionIdGenerator: undefined,
75
+ });
76
+ res.on("close", () => {
77
+ console.log("Request closed");
78
+ transport.close();
79
+ server.close();
80
+ });
81
+ const authToken = extractBearerToken(req);
82
+ if (!authToken) {
83
+ res.status(401).json({
84
+ error: "Authorization header with Bearer token is required.",
85
+ });
86
+ return;
87
+ }
88
+ await server.connect(transport);
89
+ await transport.handleRequest(Object.assign(req, {
90
+ auth: {
91
+ clientId: environmentId,
92
+ token: authToken,
93
+ scopes: [],
94
+ },
95
+ }), res, req.body);
96
+ }
97
+ catch (error) {
98
+ console.error("Error handling MCP request:", error);
99
+ if (!res.headersSent) {
100
+ res.status(500).json({
101
+ jsonrpc: "2.0",
102
+ error: {
103
+ code: -32603,
104
+ message: "Internal server error",
105
+ },
106
+ id: null,
107
+ });
108
+ }
109
+ }
66
110
  });
67
- }
68
- async function startSSE() {
69
- const app = express();
70
- const { server } = createServer();
71
- let transport;
72
- app.get("/sse", async (_req, res) => {
73
- transport = new SSEServerTransport("/message", res);
74
- await server.connect(transport);
111
+ app.get("/:environmentId/mcp", async (_, res) => {
112
+ res.writeHead(405).end(JSON.stringify({
113
+ jsonrpc: "2.0",
114
+ error: {
115
+ code: -32000,
116
+ message: "Method not allowed.",
117
+ },
118
+ id: null,
119
+ }));
75
120
  });
76
- app.post("/message", async (req, res) => {
77
- await transport.handlePostMessage(req, res);
121
+ app.delete("/:environmentId/mcp", async (_, res) => {
122
+ res.writeHead(405).end(JSON.stringify({
123
+ jsonrpc: "2.0",
124
+ error: {
125
+ code: -32000,
126
+ message: "Method not allowed.",
127
+ },
128
+ id: null,
129
+ }));
78
130
  });
79
131
  const PORT = process.env.PORT || 3001;
80
132
  app.listen(PORT, () => {
81
- console.log(`Kontent.ai MCP Server v${version} (SSE) running on port ${PORT}`);
133
+ console.log(`Kontent.ai MCP Server v${version} (Streamable HTTP) running on port ${PORT}.
134
+ Available endpoints:
135
+ /mcp
136
+ /{environmentId}/mcp (requires Bearer authentication)`);
82
137
  });
83
138
  }
84
- async function startStdio() {
85
- const { server } = createServer();
139
+ async function startStdio(config) {
140
+ const { server } = createServer(config);
86
141
  const transport = new StdioServerTransport();
87
142
  console.log(`Kontent.ai MCP Server v${version} (stdio) starting`);
88
143
  await server.connect(transport);
89
144
  }
90
145
  async function main() {
146
+ const config = await loadAppConfiguration();
91
147
  const args = process.argv.slice(2);
92
148
  const transportType = args[0]?.toLowerCase();
93
149
  if (!transportType ||
94
- (transportType !== "stdio" &&
95
- transportType !== "sse" &&
96
- transportType !== "shttp")) {
97
- console.error("Please specify a valid transport type: stdio, sse, or shttp");
150
+ (transportType !== "stdio" && transportType !== "shttp")) {
151
+ console.error("Please specify a valid transport type: stdio or shttp");
98
152
  process.exit(1);
99
153
  }
100
154
  if (transportType === "stdio") {
101
- await startStdio();
102
- }
103
- else if (transportType === "sse") {
104
- await startSSE();
155
+ await startStdio(config);
105
156
  }
106
157
  else if (transportType === "shttp") {
107
- await startStreamableHTTP();
158
+ await startStreamableHTTP(config);
108
159
  }
109
160
  }
110
161
  main().catch((error) => {
@@ -6,9 +6,10 @@ const sourceTrackingHeaderName = "X-KC-SOURCE";
6
6
  * Creates a Kontent.ai Management API client
7
7
  * @param environmentId Optional environment ID (defaults to process.env.KONTENT_ENVIRONMENT_ID)
8
8
  * @param apiKey Optional API key (defaults to process.env.KONTENT_API_KEY)
9
+ * @param config Optional configuration object
9
10
  * @returns Management API client instance
10
11
  */
11
- export const createMapiClient = (environmentId, apiKey) => {
12
+ export const createMapiClient = (environmentId, apiKey, config) => {
12
13
  return createManagementClient({
13
14
  apiKey: apiKey ??
14
15
  process.env.KONTENT_API_KEY ??
@@ -22,5 +23,6 @@ export const createMapiClient = (environmentId, apiKey) => {
22
23
  value: `${packageJson.name};${packageJson.version}`,
23
24
  },
24
25
  ],
26
+ baseUrl: config ? `${config.manageApiUrl}v2` : undefined,
25
27
  });
26
28
  };
@@ -0,0 +1,51 @@
1
+ import { load } from "@azure/app-configuration-provider";
2
+ import { DefaultAzureCredential } from "@azure/identity";
3
+ function loadIndexedEnvVars(prefix) {
4
+ const values = [];
5
+ let index = 0;
6
+ while (true) {
7
+ const envVar = process.env[`${prefix}__${index}`];
8
+ if (!envVar)
9
+ break;
10
+ const trimmed = envVar.trim();
11
+ if (trimmed) {
12
+ values.push(trimmed);
13
+ }
14
+ index++;
15
+ }
16
+ return values;
17
+ }
18
+ function getConfigValue(configMap, key) {
19
+ const value = configMap.get(key);
20
+ return value && typeof value === "string" ? value : undefined;
21
+ }
22
+ export async function loadAppConfiguration() {
23
+ try {
24
+ const appConfigEndpoint = process.env.ConfigStore__Endpoints__0;
25
+ const labels = loadIndexedEnvVars("ConfigStore__Labels");
26
+ if (!appConfigEndpoint || labels.length === 0) {
27
+ return null;
28
+ }
29
+ const selectors = labels.flatMap((label) => [
30
+ { keyFilter: "ApplicationInsights:*", labelFilter: label },
31
+ { keyFilter: "Global:Runtime:*", labelFilter: label },
32
+ { keyFilter: "Draft:ManageApi:*", labelFilter: label },
33
+ { keyFilter: "Deliver:ApiClient:Domains:*", labelFilter: label },
34
+ ]);
35
+ const credential = new DefaultAzureCredential();
36
+ const configMap = await load(appConfigEndpoint, credential, {
37
+ selectors: selectors,
38
+ keyVaultOptions: {
39
+ credential: credential,
40
+ },
41
+ });
42
+ return {
43
+ manageApiUrl: getConfigValue(configMap, "Draft:ManageApi:Url"),
44
+ deliveryApiUrl: getConfigValue(configMap, "Deliver:ApiClient:Domains:LiveContentDomain"),
45
+ };
46
+ }
47
+ catch (error) {
48
+ console.log("Failed to load App Configuration:", error);
49
+ return null;
50
+ }
51
+ }
package/build/server.js CHANGED
@@ -30,7 +30,7 @@ import { registerTool as registerUnpublishVariantMapi } from "./tools/unpublish-
30
30
  import { registerTool as registerUpdateContentItemMapi } from "./tools/update-content-item-mapi.js";
31
31
  import { registerTool as registerUpsertLanguageVariantMapi } from "./tools/upsert-language-variant-mapi.js";
32
32
  // Create server instance
33
- export const createServer = () => {
33
+ export const createServer = (config) => {
34
34
  const server = new McpServer({
35
35
  name: "kontent-ai",
36
36
  version: packageJson.version,
@@ -41,33 +41,33 @@ export const createServer = () => {
41
41
  });
42
42
  // Register all tools
43
43
  registerGetInitialContext(server);
44
- registerGetItemMapi(server);
45
- registerGetItemDapi(server);
46
- registerGetVariantMapi(server);
47
- registerGetTypeMapi(server);
48
- registerListContentTypesMapi(server);
49
- registerDeleteContentTypeMapi(server);
50
- registerListLanguagesMapi(server);
51
- registerGetAssetMapi(server);
52
- registerListAssetsMapi(server);
53
- registerAddContentTypeMapi(server);
54
- registerPatchContentTypeMapi(server);
55
- registerAddContentTypeSnippetMapi(server);
56
- registerGetTypeSnippetMapi(server);
57
- registerListContentTypeSnippetsMapi(server);
58
- registerAddTaxonomyGroupMapi(server);
59
- registerListTaxonomyGroupsMapi(server);
60
- registerGetTaxonomyGroupMapi(server);
61
- registerAddContentItemMapi(server);
62
- registerUpdateContentItemMapi(server);
63
- registerDeleteContentItemMapi(server);
64
- registerUpsertLanguageVariantMapi(server);
65
- registerCreateVariantVersionMapi(server);
66
- registerDeleteLanguageVariantMapi(server);
67
- registerListWorkflowsMapi(server);
68
- registerChangeVariantWorkflowStepMapi(server);
69
- registerFilterVariantsMapi(server);
70
- registerPublishVariantMapi(server);
71
- registerUnpublishVariantMapi(server);
44
+ registerGetItemMapi(server, config);
45
+ registerGetItemDapi(server, config);
46
+ registerGetVariantMapi(server, config);
47
+ registerGetTypeMapi(server, config);
48
+ registerListContentTypesMapi(server, config);
49
+ registerDeleteContentTypeMapi(server, config);
50
+ registerListLanguagesMapi(server, config);
51
+ registerGetAssetMapi(server, config);
52
+ registerListAssetsMapi(server, config);
53
+ registerAddContentTypeMapi(server, config);
54
+ registerPatchContentTypeMapi(server, config);
55
+ registerAddContentTypeSnippetMapi(server, config);
56
+ registerGetTypeSnippetMapi(server, config);
57
+ registerListContentTypeSnippetsMapi(server, config);
58
+ registerAddTaxonomyGroupMapi(server, config);
59
+ registerListTaxonomyGroupsMapi(server, config);
60
+ registerGetTaxonomyGroupMapi(server, config);
61
+ registerAddContentItemMapi(server, config);
62
+ registerUpdateContentItemMapi(server, config);
63
+ registerDeleteContentItemMapi(server, config);
64
+ registerUpsertLanguageVariantMapi(server, config);
65
+ registerCreateVariantVersionMapi(server, config);
66
+ registerDeleteLanguageVariantMapi(server, config);
67
+ registerListWorkflowsMapi(server, config);
68
+ registerChangeVariantWorkflowStepMapi(server, config);
69
+ registerFilterVariantsMapi(server, config);
70
+ registerPublishVariantMapi(server, config);
71
+ registerUnpublishVariantMapi(server, config);
72
72
  return { server };
73
73
  };
@@ -2,7 +2,7 @@ import { z } from "zod";
2
2
  import { createMapiClient } from "../clients/kontentClients.js";
3
3
  import { handleMcpToolError } from "../utils/errorHandler.js";
4
4
  import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
5
- export const registerTool = (server) => {
5
+ export const registerTool = (server, config) => {
6
6
  server.tool("add-content-item-mapi", "Add new Kontent.ai content item via Management API. This creates the content item structure but does not add content to language variants. Use upsert-language-variant-mapi to add content to the item.", {
7
7
  name: z
8
8
  .string()
@@ -32,8 +32,8 @@ export const registerTool = (server) => {
32
32
  })
33
33
  .optional()
34
34
  .describe("Reference to a collection by id, codename, or external_id (optional)"),
35
- }, async ({ name, type, codename, external_id, collection }) => {
36
- const client = createMapiClient();
35
+ }, async ({ name, type, codename, external_id, collection }, { authInfo: { token, clientId } = {} }) => {
36
+ const client = createMapiClient(clientId, token, config);
37
37
  try {
38
38
  const response = await client
39
39
  .addContentItem()
@@ -3,7 +3,7 @@ import { createMapiClient } from "../clients/kontentClients.js";
3
3
  import { contentGroupSchema, elementSchema, } from "../schemas/contentTypeSchemas.js";
4
4
  import { handleMcpToolError } from "../utils/errorHandler.js";
5
5
  import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
6
- export const registerTool = (server) => {
6
+ export const registerTool = (server, config) => {
7
7
  server.tool("add-content-type-mapi", "Add new Kontent.ai content type via Management API", {
8
8
  name: z.string().describe("Display name of the content type"),
9
9
  codename: z
@@ -21,8 +21,8 @@ export const registerTool = (server) => {
21
21
  .array(contentGroupSchema)
22
22
  .optional()
23
23
  .describe("Array of content groups (optional)"),
24
- }, async ({ name, codename, external_id, elements, content_groups }) => {
25
- const client = createMapiClient();
24
+ }, async ({ name, codename, external_id, elements, content_groups }, { authInfo: { token, clientId } = {} }) => {
25
+ const client = createMapiClient(clientId, token, config);
26
26
  try {
27
27
  const response = await client
28
28
  .addContentType()
@@ -3,7 +3,7 @@ import { createMapiClient } from "../clients/kontentClients.js";
3
3
  import { snippetElementSchema } from "../schemas/contentTypeSchemas.js";
4
4
  import { handleMcpToolError } from "../utils/errorHandler.js";
5
5
  import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
6
- export const registerTool = (server) => {
6
+ export const registerTool = (server, config) => {
7
7
  server.tool("add-content-type-snippet-mapi", "Add new Kontent.ai content type snippet via Management API", {
8
8
  name: z.string().describe("Display name of the content type snippet"),
9
9
  codename: z
@@ -17,8 +17,8 @@ export const registerTool = (server) => {
17
17
  elements: z
18
18
  .array(snippetElementSchema)
19
19
  .describe("Array of elements that define the structure of the content type snippet"),
20
- }, async ({ name, codename, external_id, elements }) => {
21
- const client = createMapiClient();
20
+ }, async ({ name, codename, external_id, elements }, { authInfo: { token, clientId } = {} }) => {
21
+ const client = createMapiClient(clientId, token, config);
22
22
  try {
23
23
  const response = await client
24
24
  .addContentTypeSnippet()
@@ -2,9 +2,9 @@ import { createMapiClient } from "../clients/kontentClients.js";
2
2
  import { taxonomyGroupSchemas } from "../schemas/taxonomySchemas.js";
3
3
  import { handleMcpToolError } from "../utils/errorHandler.js";
4
4
  import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
5
- export const registerTool = (server) => {
6
- server.tool("add-taxonomy-group-mapi", "Add new Kontent.ai taxonomy group via Management API", taxonomyGroupSchemas, async (taxonomyGroup) => {
7
- const client = createMapiClient();
5
+ export const registerTool = (server, config) => {
6
+ server.tool("add-taxonomy-group-mapi", "Add new Kontent.ai taxonomy group via Management API", taxonomyGroupSchemas, async (taxonomyGroup, { authInfo: { token, clientId } = {} }) => {
7
+ const client = createMapiClient(clientId, token, config);
8
8
  try {
9
9
  const response = await client
10
10
  .addTaxonomy()
@@ -2,7 +2,7 @@ import { z } from "zod";
2
2
  import { createMapiClient } from "../clients/kontentClients.js";
3
3
  import { handleMcpToolError } from "../utils/errorHandler.js";
4
4
  import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
5
- export const registerTool = (server) => {
5
+ export const registerTool = (server, config) => {
6
6
  server.tool("change-variant-workflow-step-mapi", "Change the workflow step of a language variant in Kontent.ai. This operation moves a language variant to a different step in the workflow, enabling content lifecycle management such as moving content from draft to review, review to published, etc.", {
7
7
  itemId: z
8
8
  .string()
@@ -20,8 +20,8 @@ export const registerTool = (server) => {
20
20
  .string()
21
21
  .uuid()
22
22
  .describe("Internal ID (UUID) of the target workflow step. This must be a valid step ID from the specified workflow. Common steps include Draft, Review, Published, and Archived, but the actual IDs depend on your specific workflow configuration"),
23
- }, async ({ itemId, languageId, workflowId, workflowStepId }) => {
24
- const client = createMapiClient();
23
+ }, async ({ itemId, languageId, workflowId, workflowStepId }, { authInfo: { token, clientId } = {} }) => {
24
+ const client = createMapiClient(clientId, token, config);
25
25
  try {
26
26
  const response = await client
27
27
  .changeWorkflowOfLanguageVariant()
@@ -2,7 +2,7 @@ import { z } from "zod";
2
2
  import { createMapiClient } from "../clients/kontentClients.js";
3
3
  import { handleMcpToolError } from "../utils/errorHandler.js";
4
4
  import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
5
- export const registerTool = (server) => {
5
+ export const registerTool = (server, config) => {
6
6
  server.tool("create-variant-version-mapi", "Create new version of Kontent.ai language variant via Management API. This operation creates a new version of an existing language variant, useful for content versioning and creating new drafts from published content.", {
7
7
  itemId: z
8
8
  .string()
@@ -12,8 +12,8 @@ export const registerTool = (server) => {
12
12
  .string()
13
13
  .uuid()
14
14
  .describe("Internal ID (UUID) of the language variant to create a new version of. Use '00000000-0000-0000-0000-000000000000' for the default language"),
15
- }, async ({ itemId, languageId }) => {
16
- const client = createMapiClient();
15
+ }, async ({ itemId, languageId }, { authInfo: { token, clientId } = {} }) => {
16
+ const client = createMapiClient(clientId, token, config);
17
17
  try {
18
18
  const response = await client
19
19
  .createNewVersionOfLanguageVariant()
@@ -2,11 +2,11 @@ import { z } from "zod";
2
2
  import { createMapiClient } from "../clients/kontentClients.js";
3
3
  import { handleMcpToolError } from "../utils/errorHandler.js";
4
4
  import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
5
- export const registerTool = (server) => {
5
+ export const registerTool = (server, config) => {
6
6
  server.tool("delete-content-item-mapi", "Delete Kontent.ai content item by internal ID from Management API", {
7
7
  id: z.string().describe("Internal ID of the content item to delete"),
8
- }, async ({ id }) => {
9
- const client = createMapiClient();
8
+ }, async ({ id }, { authInfo: { token, clientId } = {} }) => {
9
+ const client = createMapiClient(clientId, token, config);
10
10
  try {
11
11
  const response = await client
12
12
  .deleteContentItem()
@@ -2,11 +2,11 @@ import { z } from "zod";
2
2
  import { createMapiClient } from "../clients/kontentClients.js";
3
3
  import { handleMcpToolError } from "../utils/errorHandler.js";
4
4
  import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
5
- export const registerTool = (server) => {
5
+ export const registerTool = (server, config) => {
6
6
  server.tool("delete-content-type-mapi", "Delete a content type by codename from Management API", {
7
7
  codename: z.string().describe("Codename of the content type to delete"),
8
- }, async ({ codename }) => {
9
- const client = createMapiClient();
8
+ }, async ({ codename }, { authInfo: { token, clientId } = {} }) => {
9
+ const client = createMapiClient(clientId, token, config);
10
10
  try {
11
11
  const response = await client
12
12
  .deleteContentType()
@@ -2,14 +2,14 @@ import { z } from "zod";
2
2
  import { createMapiClient } from "../clients/kontentClients.js";
3
3
  import { handleMcpToolError } from "../utils/errorHandler.js";
4
4
  import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
5
- export const registerTool = (server) => {
5
+ export const registerTool = (server, config) => {
6
6
  server.tool("delete-language-variant-mapi", "Delete Kontent.ai language variant from Management API", {
7
7
  itemId: z.string().describe("Internal ID of the content item"),
8
8
  languageId: z
9
9
  .string()
10
10
  .describe("Internal ID of the language variant to delete"),
11
- }, async ({ itemId, languageId }) => {
12
- const client = createMapiClient();
11
+ }, async ({ itemId, languageId }, { authInfo: { token, clientId } = {} }) => {
12
+ const client = createMapiClient(clientId, token, config);
13
13
  try {
14
14
  const response = await client
15
15
  .deleteLanguageVariant()
@@ -2,8 +2,8 @@ import { filterVariantsSchema } from "../schemas/filterVariantSchemas.js";
2
2
  import { handleMcpToolError } from "../utils/errorHandler.js";
3
3
  import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
4
4
  import { throwError } from "../utils/throwError.js";
5
- export const registerTool = (server) => {
6
- server.tool("filter-variants-mapi", "Search and filter Kontent.ai language variants of content items using Management API", filterVariantsSchema.shape, async ({ search_phrase, content_types, contributors, has_no_contributors, completion_statuses, language, workflow_steps, taxonomy_groups, order_by, order_direction, continuation_token, }) => {
5
+ export const registerTool = (server, config) => {
6
+ server.tool("filter-variants-mapi", "Search and filter Kontent.ai language variants of content items using Management API", filterVariantsSchema.shape, async ({ search_phrase, content_types, contributors, has_no_contributors, completion_statuses, language, workflow_steps, taxonomy_groups, order_by, order_direction, continuation_token, }, { authInfo: { token, clientId } = {} }) => {
7
7
  try {
8
8
  const requestPayload = {
9
9
  filters: {
@@ -23,12 +23,18 @@ export const registerTool = (server) => {
23
23
  }
24
24
  : null,
25
25
  };
26
- const environmentId = process.env.KONTENT_ENVIRONMENT_ID;
27
- const apiKey = process.env.KONTENT_API_KEY;
28
- if (!environmentId || !apiKey) {
29
- throwError("Missing required environment variables");
26
+ const environmentId = clientId ?? process.env.KONTENT_ENVIRONMENT_ID;
27
+ if (!environmentId) {
28
+ throwError("Missing required environment ID");
30
29
  }
31
- const url = `https://manage.kontent.ai/v2/projects/${environmentId}/early-access/variants/filter`;
30
+ const apiKey = token ?? process.env.KONTENT_API_KEY;
31
+ if (!apiKey) {
32
+ throwError("Missing required API key");
33
+ }
34
+ const baseUrl = config
35
+ ? `${config.manageApiUrl}`
36
+ : `https://manage.kontent.ai/`;
37
+ const url = `${baseUrl}v2/projects/${environmentId}/early-access/variants/filter`;
32
38
  const headers = {
33
39
  Authorization: `Bearer ${apiKey}`,
34
40
  "Content-Type": "application/json",
@@ -2,11 +2,11 @@ import { z } from "zod";
2
2
  import { createMapiClient } from "../clients/kontentClients.js";
3
3
  import { handleMcpToolError } from "../utils/errorHandler.js";
4
4
  import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
5
- export const registerTool = (server) => {
5
+ export const registerTool = (server, config) => {
6
6
  server.tool("get-asset-mapi", "Get a specific Kontent.ai asset by internal ID from Management API", {
7
7
  assetId: z.string().describe("Internal ID of the asset to retrieve"),
8
- }, async ({ assetId }) => {
9
- const client = createMapiClient();
8
+ }, async ({ assetId }, { authInfo: { token, clientId } = {} }) => {
9
+ const client = createMapiClient(clientId, token, config);
10
10
  try {
11
11
  const response = await client
12
12
  .viewAsset()
@@ -2,7 +2,7 @@ import { createDeliveryClient } from "@kontent-ai/delivery-sdk";
2
2
  import { z } from "zod";
3
3
  import { handleMcpToolError } from "../utils/errorHandler.js";
4
4
  import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
5
- export const registerTool = (server) => {
5
+ export const registerTool = (server, config) => {
6
6
  server.tool("get-item-dapi", "Get Kontent.ai item by codename from Delivery API", {
7
7
  codename: z.string().describe("Codename of the item to get"),
8
8
  environmentId: z
@@ -11,6 +11,9 @@ export const registerTool = (server) => {
11
11
  }, async ({ codename, environmentId }) => {
12
12
  const client = createDeliveryClient({
13
13
  environmentId,
14
+ proxy: {
15
+ baseUrl: config ? `https://${config.deliveryApiUrl}` : undefined,
16
+ },
14
17
  });
15
18
  try {
16
19
  const response = await client.item(codename).toPromise();
@@ -2,11 +2,11 @@ import { z } from "zod";
2
2
  import { createMapiClient } from "../clients/kontentClients.js";
3
3
  import { handleMcpToolError } from "../utils/errorHandler.js";
4
4
  import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
5
- export const registerTool = (server) => {
5
+ export const registerTool = (server, config) => {
6
6
  server.tool("get-item-mapi", "Get Kontent.ai item by internal ID from Management API", {
7
7
  id: z.string().describe("Internal ID of the item to get"),
8
- }, async ({ id }) => {
9
- const client = createMapiClient();
8
+ }, async ({ id }, { authInfo: { token, clientId } = {} }) => {
9
+ const client = createMapiClient(clientId, token, config);
10
10
  try {
11
11
  const response = await client
12
12
  .viewContentItem()
@@ -2,11 +2,11 @@ import { z } from "zod";
2
2
  import { createMapiClient } from "../clients/kontentClients.js";
3
3
  import { handleMcpToolError } from "../utils/errorHandler.js";
4
4
  import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
5
- export const registerTool = (server) => {
5
+ export const registerTool = (server, config) => {
6
6
  server.tool("get-taxonomy-group-mapi", "Get Kontent.ai taxonomy group by internal ID from Management API", {
7
7
  id: z.string().describe("Internal ID of the taxonomy group to get"),
8
- }, async ({ id }) => {
9
- const client = createMapiClient();
8
+ }, async ({ id }, { authInfo: { token, clientId } = {} }) => {
9
+ const client = createMapiClient(clientId, token, config);
10
10
  try {
11
11
  const response = await client
12
12
  .getTaxonomy()
@@ -2,11 +2,11 @@ import { z } from "zod";
2
2
  import { createMapiClient } from "../clients/kontentClients.js";
3
3
  import { handleMcpToolError } from "../utils/errorHandler.js";
4
4
  import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
5
- export const registerTool = (server) => {
5
+ export const registerTool = (server, config) => {
6
6
  server.tool("get-type-mapi", "Get Kontent.ai content type by internal ID from Management API", {
7
7
  id: z.string().describe("Internal ID of the content type to get"),
8
- }, async ({ id }) => {
9
- const client = createMapiClient();
8
+ }, async ({ id }, { authInfo: { token, clientId } = {} }) => {
9
+ const client = createMapiClient(clientId, token, config);
10
10
  try {
11
11
  const response = await client
12
12
  .viewContentType()
@@ -2,11 +2,11 @@ import { z } from "zod";
2
2
  import { createMapiClient } from "../clients/kontentClients.js";
3
3
  import { handleMcpToolError } from "../utils/errorHandler.js";
4
4
  import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
5
- export const registerTool = (server) => {
5
+ export const registerTool = (server, config) => {
6
6
  server.tool("get-type-snippet-mapi", "Get Kontent.ai content type snippet by internal ID from Management API", {
7
7
  id: z.string().describe("Internal ID of the content type snippet to get"),
8
- }, async ({ id }) => {
9
- const client = createMapiClient();
8
+ }, async ({ id }, { authInfo: { token, clientId } = {} }) => {
9
+ const client = createMapiClient(clientId, token, config);
10
10
  try {
11
11
  const response = await client
12
12
  .viewContentTypeSnippet()
@@ -2,14 +2,14 @@ import { z } from "zod";
2
2
  import { createMapiClient } from "../clients/kontentClients.js";
3
3
  import { handleMcpToolError } from "../utils/errorHandler.js";
4
4
  import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
5
- export const registerTool = (server) => {
5
+ export const registerTool = (server, config) => {
6
6
  server.tool("get-variant-mapi", "Get Kontent.ai language variant of content item from Management API", {
7
7
  itemId: z.string().describe("Internal ID of the content item"),
8
8
  languageId: z
9
9
  .string()
10
10
  .describe("Internal ID of the language variant to get"),
11
- }, async ({ itemId, languageId }) => {
12
- const client = createMapiClient();
11
+ }, async ({ itemId, languageId }, { authInfo: { token, clientId } = {} }) => {
12
+ const client = createMapiClient(clientId, token, config);
13
13
  try {
14
14
  const response = await client
15
15
  .viewLanguageVariant()
@@ -1,9 +1,9 @@
1
1
  import { createMapiClient } from "../clients/kontentClients.js";
2
2
  import { handleMcpToolError } from "../utils/errorHandler.js";
3
3
  import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
4
- export const registerTool = (server) => {
5
- server.tool("list-assets-mapi", "Get all Kontent.ai assets from Management API", {}, async () => {
6
- const client = createMapiClient();
4
+ export const registerTool = (server, config) => {
5
+ server.tool("list-assets-mapi", "Get all Kontent.ai assets from Management API", {}, async (_, { authInfo: { token, clientId } = {} }) => {
6
+ const client = createMapiClient(clientId, token, config);
7
7
  try {
8
8
  const response = await client.listAssets().toAllPromise();
9
9
  return createMcpToolSuccessResponse(response.data);
@@ -1,9 +1,9 @@
1
1
  import { createMapiClient } from "../clients/kontentClients.js";
2
2
  import { handleMcpToolError } from "../utils/errorHandler.js";
3
3
  import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
4
- export const registerTool = (server) => {
5
- server.tool("list-content-type-snippets-mapi", "Get all Kontent.ai content type snippets from Management API", {}, async () => {
6
- const client = createMapiClient();
4
+ export const registerTool = (server, config) => {
5
+ server.tool("list-content-type-snippets-mapi", "Get all Kontent.ai content type snippets from Management API", {}, async (_, { authInfo: { token, clientId } = {} }) => {
6
+ const client = createMapiClient(clientId, token, config);
7
7
  try {
8
8
  const response = await client.listContentTypeSnippets().toAllPromise();
9
9
  return createMcpToolSuccessResponse(response.data);
@@ -1,9 +1,9 @@
1
1
  import { createMapiClient } from "../clients/kontentClients.js";
2
2
  import { handleMcpToolError } from "../utils/errorHandler.js";
3
3
  import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
4
- export const registerTool = (server) => {
5
- server.tool("list-content-types-mapi", "Get all Kontent.ai content types from Management API", {}, async () => {
6
- const client = createMapiClient();
4
+ export const registerTool = (server, config) => {
5
+ server.tool("list-content-types-mapi", "Get all Kontent.ai content types from Management API", {}, async (_, { authInfo: { token, clientId } = {} }) => {
6
+ const client = createMapiClient(clientId, token, config);
7
7
  try {
8
8
  const response = await client.listContentTypes().toAllPromise();
9
9
  return createMcpToolSuccessResponse(response.data);
@@ -1,9 +1,9 @@
1
1
  import { createMapiClient } from "../clients/kontentClients.js";
2
2
  import { handleMcpToolError } from "../utils/errorHandler.js";
3
3
  import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
4
- export const registerTool = (server) => {
5
- server.tool("list-languages-mapi", "Get all Kontent.ai languages from Management API", {}, async () => {
6
- const client = createMapiClient();
4
+ export const registerTool = (server, config) => {
5
+ server.tool("list-languages-mapi", "Get all Kontent.ai languages from Management API", {}, async (_, { authInfo: { token, clientId } = {} }) => {
6
+ const client = createMapiClient(clientId, token, config);
7
7
  try {
8
8
  const response = await client.listLanguages().toAllPromise();
9
9
  return createMcpToolSuccessResponse(response.data);
@@ -1,9 +1,9 @@
1
1
  import { createMapiClient } from "../clients/kontentClients.js";
2
2
  import { handleMcpToolError } from "../utils/errorHandler.js";
3
3
  import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
4
- export const registerTool = (server) => {
5
- server.tool("list-taxonomy-groups-mapi", "Get all Kontent.ai taxonomy groups from Management API", {}, async () => {
6
- const client = createMapiClient();
4
+ export const registerTool = (server, config) => {
5
+ server.tool("list-taxonomy-groups-mapi", "Get all Kontent.ai taxonomy groups from Management API", {}, async (_, { authInfo: { token, clientId } = {} }) => {
6
+ const client = createMapiClient(clientId, token, config);
7
7
  try {
8
8
  const response = await client.listTaxonomies().toAllPromise();
9
9
  return createMcpToolSuccessResponse(response.data);
@@ -1,9 +1,9 @@
1
1
  import { createMapiClient } from "../clients/kontentClients.js";
2
2
  import { handleMcpToolError } from "../utils/errorHandler.js";
3
3
  import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
4
- export const registerTool = (server) => {
5
- server.tool("list-workflows-mapi", "Get all Kontent.ai workflows from Management API. Workflows define the content lifecycle stages and transitions between them.", {}, async () => {
6
- const client = createMapiClient();
4
+ export const registerTool = (server, config) => {
5
+ server.tool("list-workflows-mapi", "Get all Kontent.ai workflows from Management API. Workflows define the content lifecycle stages and transitions between them.", {}, async (_, { authInfo: { token, clientId } = {} }) => {
6
+ const client = createMapiClient(clientId, token, config);
7
7
  try {
8
8
  const response = await client.listWorkflows().toPromise();
9
9
  return createMcpToolSuccessResponse(response.data);
@@ -3,7 +3,7 @@ import { createMapiClient } from "../clients/kontentClients.js";
3
3
  import { patchOperationsSchema } from "../schemas/patchSchemas/contentTypePatchSchemas.js";
4
4
  import { handleMcpToolError } from "../utils/errorHandler.js";
5
5
  import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
6
- export const registerTool = (server) => {
6
+ export const registerTool = (server, config) => {
7
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
8
  codename: z.string().describe("Codename of the content type to update"),
9
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).
@@ -54,8 +54,8 @@ export const registerTool = (server) => {
54
54
  - Consider element ordering when using move operations
55
55
  - Use atomic operations for complex changes like removing content groups
56
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();
57
+ }, async ({ codename, operations }, { authInfo: { token, clientId } = {} }) => {
58
+ const client = createMapiClient(clientId, token, config);
59
59
  try {
60
60
  // Apply patch operations using the modifyContentType method
61
61
  const response = await client
@@ -2,7 +2,7 @@ import { z } from "zod";
2
2
  import { createMapiClient } from "../clients/kontentClients.js";
3
3
  import { handleMcpToolError } from "../utils/errorHandler.js";
4
4
  import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
5
- export const registerTool = (server) => {
5
+ export const registerTool = (server, config) => {
6
6
  server.tool("publish-variant-mapi", "Publish or schedule a language variant of a content item in Kontent.ai. This operation can either immediately publish the variant (publishing happens right now) or schedule it for publication at a specific future date and time. For immediate publishing: the variant is published immediately and becomes available through the Delivery API. For scheduled publishing: the variant moves to a 'Scheduled' workflow state and will automatically transition to 'Published' at the specified time. The variant must be in a valid state with all required fields filled and validation rules satisfied. A variant can only be published if a transition is defined between the variant's current workflow step and the Published workflow step.", {
7
7
  itemId: z
8
8
  .string()
@@ -21,8 +21,8 @@ export const registerTool = (server) => {
21
21
  .string()
22
22
  .optional()
23
23
  .describe("The timezone identifier for displaying the scheduled time in the Kontent.ai UI (e.g., 'America/New_York', 'Europe/London', 'UTC'). This parameter is used for scheduled publishing to specify the timezone context for the scheduled_to parameter. If not provided, the system will use the default timezone. This helps content creators understand when content will be published in their local context."),
24
- }, async ({ itemId, languageId, scheduledTo, displayTimezone }) => {
25
- const client = createMapiClient();
24
+ }, async ({ itemId, languageId, scheduledTo, displayTimezone }, { authInfo: { token, clientId } = {} }) => {
25
+ const client = createMapiClient(clientId, token, config);
26
26
  try {
27
27
  // Validate that displayTimezone can only be used with scheduledTo
28
28
  if (displayTimezone && !scheduledTo) {
@@ -2,7 +2,7 @@ import { z } from "zod";
2
2
  import { createMapiClient } from "../clients/kontentClients.js";
3
3
  import { handleMcpToolError } from "../utils/errorHandler.js";
4
4
  import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
5
- export const registerTool = (server) => {
5
+ export const registerTool = (server, config) => {
6
6
  server.tool("unpublish-variant-mapi", "Unpublish or schedule unpublishing of a language variant of a content item in Kontent.ai. This operation can either immediately unpublish the variant (making it unavailable through the Delivery API) or schedule it for unpublishing at a specific future date and time. For immediate unpublishing: the variant is unpublished right away and moves to the 'Archived' workflow step, becoming unavailable through the Delivery API. For scheduled unpublishing: the variant remains published but is scheduled to be automatically unpublished at the specified time. The variant must currently be in the 'Published' state for this operation to succeed.", {
7
7
  itemId: z
8
8
  .string()
@@ -21,8 +21,8 @@ export const registerTool = (server) => {
21
21
  .string()
22
22
  .optional()
23
23
  .describe("The timezone identifier for displaying the scheduled time in the Kontent.ai UI (e.g., 'America/New_York', 'Europe/London', 'UTC'). This parameter is used for scheduled unpublishing to specify the timezone context for the scheduled_to parameter. If not provided, the system will use the default timezone. This helps content creators understand when content will be unpublished in their local context."),
24
- }, async ({ itemId, languageId, scheduledTo, displayTimezone }) => {
25
- const client = createMapiClient();
24
+ }, async ({ itemId, languageId, scheduledTo, displayTimezone }, { authInfo: { token, clientId } = {} }) => {
25
+ const client = createMapiClient(clientId, token, config);
26
26
  try {
27
27
  // Validate that displayTimezone can only be used with scheduledTo
28
28
  if (displayTimezone && !scheduledTo) {
@@ -2,7 +2,7 @@ import { z } from "zod";
2
2
  import { createMapiClient } from "../clients/kontentClients.js";
3
3
  import { handleMcpToolError } from "../utils/errorHandler.js";
4
4
  import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
5
- export const registerTool = (server) => {
5
+ export const registerTool = (server, config) => {
6
6
  server.tool("update-content-item-mapi", "Update existing Kontent.ai content item by internal ID via Management API. The content item must already exist - this tool will not create new items.", {
7
7
  id: z.string().describe("Internal ID of the content item to update"),
8
8
  name: z
@@ -19,8 +19,8 @@ export const registerTool = (server) => {
19
19
  })
20
20
  .optional()
21
21
  .describe("Reference to a collection by id, codename, or external_id (optional)"),
22
- }, async ({ id, name, collection }) => {
23
- const client = createMapiClient();
22
+ }, async ({ id, name, collection }, { authInfo: { token, clientId } = {} }) => {
23
+ const client = createMapiClient(clientId, token, config);
24
24
  try {
25
25
  // First, verify the item exists by trying to get it
26
26
  await client.viewContentItem().byItemId(id).toPromise();
@@ -3,7 +3,7 @@ import { createMapiClient } from "../clients/kontentClients.js";
3
3
  import { languageVariantElementSchema } from "../schemas/contentItemSchemas.js";
4
4
  import { handleMcpToolError } from "../utils/errorHandler.js";
5
5
  import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
6
- export const registerTool = (server) => {
6
+ export const registerTool = (server, config) => {
7
7
  server.tool("upsert-language-variant-mapi", "Create or update Kontent.ai language variant of a content item via Management API. This adds actual content to the content item elements. When updating an existing variant, only the provided elements will be modified.", {
8
8
  itemId: z.string().describe("Internal ID of the content item"),
9
9
  languageId: z
@@ -16,8 +16,8 @@ export const registerTool = (server) => {
16
16
  .string()
17
17
  .optional()
18
18
  .describe("Internal ID of the workflow step (optional)"),
19
- }, async ({ itemId, languageId, elements, workflow_step_id }) => {
20
- const client = createMapiClient();
19
+ }, async ({ itemId, languageId, elements, workflow_step_id }, { authInfo: { token, clientId } = {} }) => {
20
+ const client = createMapiClient(clientId, token, config);
21
21
  const data = {
22
22
  elements,
23
23
  };
@@ -50,7 +50,7 @@ export const handleMcpToolError = (error, context) => {
50
50
  content: [
51
51
  {
52
52
  type: "text",
53
- text: `${contextPrefix}HTTP Error ${error.response.status}: ${error.response.statusText || "Unknown HTTP error"}\n\nResponse: ${JSON.stringify(error.response.data, null, 2)}`,
53
+ text: `${contextPrefix}HTTP Error ${error.response.status}: ${error.response.statusText || "Unknown HTTP error"}\n\nResponse: ${JSON.stringify(error.response.data)}`,
54
54
  },
55
55
  ],
56
56
  isError: true,
@@ -61,7 +61,7 @@ export const handleMcpToolError = (error, context) => {
61
61
  content: [
62
62
  {
63
63
  type: "text",
64
- text: `${contextPrefix}Unexpected error: ${error instanceof Error ? error.message : "Unknown error occurred"}\n\nFull error: ${JSON.stringify(error, null, 2)}`,
64
+ text: `${contextPrefix}Unexpected error: ${error instanceof Error ? error.message : "Unknown error occurred"}\n\nFull error: ${JSON.stringify(error)}`,
65
65
  },
66
66
  ],
67
67
  isError: true,
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Extracts Bearer token from request authorization header
3
+ * @param req Express request object
4
+ * @returns Bearer token string or null if not found
5
+ */
6
+ export const extractBearerToken = (req) => {
7
+ const authHeader = req.headers.authorization;
8
+ if (authHeader?.startsWith("Bearer ")) {
9
+ return authHeader.substring(7); // Remove 'Bearer ' prefix
10
+ }
11
+ return null;
12
+ };
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Validates if a string is a valid GUID format
3
+ * @param guid String to validate
4
+ * @returns true if valid GUID format, false otherwise
5
+ */
6
+ export const isValidGuid = (guid) => {
7
+ const guidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
8
+ return guidRegex.test(guid);
9
+ };
@@ -11,7 +11,7 @@ export const createMcpToolSuccessResponse = (data) => {
11
11
  content: [
12
12
  {
13
13
  type: "text",
14
- text: JSON.stringify(data, null, 2),
14
+ text: JSON.stringify(data),
15
15
  },
16
16
  ],
17
17
  };
package/package.json CHANGED
@@ -1,14 +1,12 @@
1
1
  {
2
2
  "name": "@kontent-ai/mcp-server",
3
- "version": "0.16.0",
3
+ "version": "0.18.0",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "build": "rimraf build && tsc",
7
7
  "start:stdio": "node build/bin.js stdio",
8
- "start:sse": "node build/bin.js sse",
9
8
  "start:shttp": "node build/bin.js shttp",
10
9
  "dev:stdio": "tsx watch src/bin.ts stdio",
11
- "dev:sse": "tsx watch src/bin.ts sse",
12
10
  "dev:shttp": "tsx watch src/bin.ts shttp",
13
11
  "format": "cross-env node node_modules/@biomejs/biome/bin/biome ci ./ --config-path=./biome.json",
14
12
  "format:fix": "cross-env node node_modules/@biomejs/biome/bin/biome check ./ --fix --unsafe --config-path=./biome.json"
@@ -23,9 +21,13 @@
23
21
  "author": "Jiri Lojda",
24
22
  "license": "MIT",
25
23
  "dependencies": {
24
+ "@azure/app-configuration": "^1.9.0",
25
+ "@azure/app-configuration-provider": "^2.2.0",
26
+ "@azure/identity": "^4.11.1",
26
27
  "@kontent-ai/delivery-sdk": "^16.2.0",
27
28
  "@kontent-ai/management-sdk": "^7.9.0",
28
29
  "@modelcontextprotocol/sdk": "^1.12.0",
30
+ "applicationinsights": "^2.9.8",
29
31
  "dotenv": "^16.5.0",
30
32
  "express": "^5.1.0",
31
33
  "zod": "^3.25.30"