@kontent-ai/mcp-server 0.16.0 → 0.17.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 +112 -24
- package/build/bin.js +73 -24
- package/build/tools/add-content-item-mapi.js +2 -2
- package/build/tools/add-content-type-mapi.js +2 -2
- package/build/tools/add-content-type-snippet-mapi.js +2 -2
- package/build/tools/add-taxonomy-group-mapi.js +2 -2
- package/build/tools/change-variant-workflow-step-mapi.js +2 -2
- package/build/tools/create-variant-version-mapi.js +2 -2
- package/build/tools/delete-content-item-mapi.js +2 -2
- package/build/tools/delete-content-type-mapi.js +2 -2
- package/build/tools/delete-language-variant-mapi.js +2 -2
- package/build/tools/filter-variants-mapi.js +8 -5
- package/build/tools/get-asset-mapi.js +2 -2
- package/build/tools/get-item-mapi.js +2 -2
- package/build/tools/get-taxonomy-group-mapi.js +2 -2
- package/build/tools/get-type-mapi.js +2 -2
- package/build/tools/get-type-snippet-mapi.js +2 -2
- package/build/tools/get-variant-mapi.js +2 -2
- package/build/tools/list-assets-mapi.js +2 -2
- package/build/tools/list-content-type-snippets-mapi.js +2 -2
- package/build/tools/list-content-types-mapi.js +2 -2
- package/build/tools/list-languages-mapi.js +2 -2
- package/build/tools/list-taxonomy-groups-mapi.js +2 -2
- package/build/tools/list-workflows-mapi.js +2 -2
- package/build/tools/patch-content-type-mapi.js +2 -2
- package/build/tools/publish-variant-mapi.js +2 -2
- package/build/tools/unpublish-variant-mapi.js +2 -2
- package/build/tools/update-content-item-mapi.js +2 -2
- package/build/tools/upsert-language-variant-mapi.js +2 -2
- package/build/utils/errorHandler.js +2 -2
- package/build/utils/extractBearerToken.js +12 -0
- package/build/utils/isValidGuid.js +9 -0
- package/build/utils/responseHelper.js +1 -1
- package/package.json +1 -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
|
-
####
|
|
51
|
+
#### Streamable HTTP Transport
|
|
52
52
|
|
|
53
53
|
```bash
|
|
54
|
-
npx @kontent-ai/mcp-server@latest
|
|
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
|
|
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
|
|
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
|
-
###
|
|
153
|
+
### 🌊 Streamable HTTP Transport
|
|
142
154
|
|
|
143
|
-
For
|
|
155
|
+
For Streamable HTTP transport, first start the server:
|
|
144
156
|
|
|
145
157
|
```bash
|
|
146
|
-
npx @kontent-ai/mcp-server@latest
|
|
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-
|
|
160
|
-
"url": "http://localhost:3001/
|
|
173
|
+
"kontent-ai-http": {
|
|
174
|
+
"url": "http://localhost:3001/mcp"
|
|
161
175
|
}
|
|
162
176
|
}
|
|
163
177
|
```
|
|
164
178
|
|
|
165
|
-
|
|
179
|
+
#### Multi-Tenant Mode
|
|
166
180
|
|
|
167
|
-
|
|
181
|
+
No environment variables required. The server accepts requests for multiple environments using URL path parameters and Bearer authentication.
|
|
168
182
|
|
|
169
|
-
|
|
170
|
-
|
|
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
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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
|
-
|
|
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
|
-
"
|
|
184
|
-
"
|
|
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
|
|
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,11 +1,12 @@
|
|
|
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" };
|
|
8
7
|
import { createServer } from "./server.js";
|
|
8
|
+
import { extractBearerToken } from "./utils/extractBearerToken.js";
|
|
9
|
+
import { isValidGuid } from "./utils/isValidGuid.js";
|
|
9
10
|
const version = packageJson.version;
|
|
10
11
|
async function startStreamableHTTP() {
|
|
11
12
|
const app = express();
|
|
@@ -39,7 +40,6 @@ async function startStreamableHTTP() {
|
|
|
39
40
|
}
|
|
40
41
|
});
|
|
41
42
|
app.get("/mcp", async (_, res) => {
|
|
42
|
-
console.log("Received GET MCP request");
|
|
43
43
|
res.writeHead(405).end(JSON.stringify({
|
|
44
44
|
jsonrpc: "2.0",
|
|
45
45
|
error: {
|
|
@@ -50,7 +50,6 @@ async function startStreamableHTTP() {
|
|
|
50
50
|
}));
|
|
51
51
|
});
|
|
52
52
|
app.delete("/mcp", async (_, res) => {
|
|
53
|
-
console.log("Received DELETE MCP request");
|
|
54
53
|
res.writeHead(405).end(JSON.stringify({
|
|
55
54
|
jsonrpc: "2.0",
|
|
56
55
|
error: {
|
|
@@ -60,25 +59,80 @@ async function startStreamableHTTP() {
|
|
|
60
59
|
id: null,
|
|
61
60
|
}));
|
|
62
61
|
});
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
62
|
+
app.post("/:environmentId/mcp", async (req, res) => {
|
|
63
|
+
try {
|
|
64
|
+
const { environmentId } = req.params;
|
|
65
|
+
if (!isValidGuid(environmentId)) {
|
|
66
|
+
res.status(400).json({
|
|
67
|
+
error: "Invalid environment ID format. Must be a valid GUID.",
|
|
68
|
+
});
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const { server } = createServer();
|
|
72
|
+
const transport = new StreamableHTTPServerTransport({
|
|
73
|
+
sessionIdGenerator: undefined,
|
|
74
|
+
});
|
|
75
|
+
res.on("close", () => {
|
|
76
|
+
console.log("Request closed");
|
|
77
|
+
transport.close();
|
|
78
|
+
server.close();
|
|
79
|
+
});
|
|
80
|
+
const authToken = extractBearerToken(req);
|
|
81
|
+
if (!authToken) {
|
|
82
|
+
res.status(401).json({
|
|
83
|
+
error: "Authorization header with Bearer token is required.",
|
|
84
|
+
});
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
await server.connect(transport);
|
|
88
|
+
await transport.handleRequest(Object.assign(req, {
|
|
89
|
+
auth: {
|
|
90
|
+
clientId: environmentId,
|
|
91
|
+
token: authToken,
|
|
92
|
+
scopes: [],
|
|
93
|
+
},
|
|
94
|
+
}), res, req.body);
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
console.error("Error handling MCP request:", error);
|
|
98
|
+
if (!res.headersSent) {
|
|
99
|
+
res.status(500).json({
|
|
100
|
+
jsonrpc: "2.0",
|
|
101
|
+
error: {
|
|
102
|
+
code: -32603,
|
|
103
|
+
message: "Internal server error",
|
|
104
|
+
},
|
|
105
|
+
id: null,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
66
109
|
});
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
110
|
+
app.get("/:environmentId/mcp", async (_, res) => {
|
|
111
|
+
res.writeHead(405).end(JSON.stringify({
|
|
112
|
+
jsonrpc: "2.0",
|
|
113
|
+
error: {
|
|
114
|
+
code: -32000,
|
|
115
|
+
message: "Method not allowed.",
|
|
116
|
+
},
|
|
117
|
+
id: null,
|
|
118
|
+
}));
|
|
75
119
|
});
|
|
76
|
-
app.
|
|
77
|
-
|
|
120
|
+
app.delete("/:environmentId/mcp", async (_, res) => {
|
|
121
|
+
res.writeHead(405).end(JSON.stringify({
|
|
122
|
+
jsonrpc: "2.0",
|
|
123
|
+
error: {
|
|
124
|
+
code: -32000,
|
|
125
|
+
message: "Method not allowed.",
|
|
126
|
+
},
|
|
127
|
+
id: null,
|
|
128
|
+
}));
|
|
78
129
|
});
|
|
79
130
|
const PORT = process.env.PORT || 3001;
|
|
80
131
|
app.listen(PORT, () => {
|
|
81
|
-
console.log(`Kontent.ai MCP Server v${version} (
|
|
132
|
+
console.log(`Kontent.ai MCP Server v${version} (Streamable HTTP) running on port ${PORT}.
|
|
133
|
+
Available endpoints:
|
|
134
|
+
/mcp
|
|
135
|
+
/{environmentId}/mcp (requires Bearer authentication)`);
|
|
82
136
|
});
|
|
83
137
|
}
|
|
84
138
|
async function startStdio() {
|
|
@@ -91,18 +145,13 @@ async function main() {
|
|
|
91
145
|
const args = process.argv.slice(2);
|
|
92
146
|
const transportType = args[0]?.toLowerCase();
|
|
93
147
|
if (!transportType ||
|
|
94
|
-
(transportType !== "stdio" &&
|
|
95
|
-
|
|
96
|
-
transportType !== "shttp")) {
|
|
97
|
-
console.error("Please specify a valid transport type: stdio, sse, or shttp");
|
|
148
|
+
(transportType !== "stdio" && transportType !== "shttp")) {
|
|
149
|
+
console.error("Please specify a valid transport type: stdio or shttp");
|
|
98
150
|
process.exit(1);
|
|
99
151
|
}
|
|
100
152
|
if (transportType === "stdio") {
|
|
101
153
|
await startStdio();
|
|
102
154
|
}
|
|
103
|
-
else if (transportType === "sse") {
|
|
104
|
-
await startSSE();
|
|
105
|
-
}
|
|
106
155
|
else if (transportType === "shttp") {
|
|
107
156
|
await startStreamableHTTP();
|
|
108
157
|
}
|
|
@@ -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);
|
|
37
37
|
try {
|
|
38
38
|
const response = await client
|
|
39
39
|
.addContentItem()
|
|
@@ -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);
|
|
26
26
|
try {
|
|
27
27
|
const response = await client
|
|
28
28
|
.addContentType()
|
|
@@ -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);
|
|
22
22
|
try {
|
|
23
23
|
const response = await client
|
|
24
24
|
.addContentTypeSnippet()
|
|
@@ -3,8 +3,8 @@ import { taxonomyGroupSchemas } from "../schemas/taxonomySchemas.js";
|
|
|
3
3
|
import { handleMcpToolError } from "../utils/errorHandler.js";
|
|
4
4
|
import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
|
|
5
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();
|
|
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);
|
|
8
8
|
try {
|
|
9
9
|
const response = await client
|
|
10
10
|
.addTaxonomy()
|
|
@@ -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);
|
|
25
25
|
try {
|
|
26
26
|
const response = await client
|
|
27
27
|
.changeWorkflowOfLanguageVariant()
|
|
@@ -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);
|
|
17
17
|
try {
|
|
18
18
|
const response = await client
|
|
19
19
|
.createNewVersionOfLanguageVariant()
|
|
@@ -5,8 +5,8 @@ import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
|
|
|
5
5
|
export const registerTool = (server) => {
|
|
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);
|
|
10
10
|
try {
|
|
11
11
|
const response = await client
|
|
12
12
|
.deleteContentItem()
|
|
@@ -5,8 +5,8 @@ import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
|
|
|
5
5
|
export const registerTool = (server) => {
|
|
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);
|
|
10
10
|
try {
|
|
11
11
|
const response = await client
|
|
12
12
|
.deleteContentType()
|
|
@@ -8,8 +8,8 @@ export const registerTool = (server) => {
|
|
|
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);
|
|
13
13
|
try {
|
|
14
14
|
const response = await client
|
|
15
15
|
.deleteLanguageVariant()
|
|
@@ -3,7 +3,7 @@ import { handleMcpToolError } from "../utils/errorHandler.js";
|
|
|
3
3
|
import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
|
|
4
4
|
import { throwError } from "../utils/throwError.js";
|
|
5
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, }) => {
|
|
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,10 +23,13 @@ export const registerTool = (server) => {
|
|
|
23
23
|
}
|
|
24
24
|
: null,
|
|
25
25
|
};
|
|
26
|
-
const environmentId = process.env.KONTENT_ENVIRONMENT_ID;
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
const environmentId = clientId ?? process.env.KONTENT_ENVIRONMENT_ID;
|
|
27
|
+
if (!environmentId) {
|
|
28
|
+
throwError("Missing required environment ID");
|
|
29
|
+
}
|
|
30
|
+
const apiKey = token ?? process.env.KONTENT_API_KEY;
|
|
31
|
+
if (!apiKey) {
|
|
32
|
+
throwError("Missing required API key");
|
|
30
33
|
}
|
|
31
34
|
const url = `https://manage.kontent.ai/v2/projects/${environmentId}/early-access/variants/filter`;
|
|
32
35
|
const headers = {
|
|
@@ -5,8 +5,8 @@ import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
|
|
|
5
5
|
export const registerTool = (server) => {
|
|
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);
|
|
10
10
|
try {
|
|
11
11
|
const response = await client
|
|
12
12
|
.viewAsset()
|
|
@@ -5,8 +5,8 @@ import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
|
|
|
5
5
|
export const registerTool = (server) => {
|
|
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);
|
|
10
10
|
try {
|
|
11
11
|
const response = await client
|
|
12
12
|
.viewContentItem()
|
|
@@ -5,8 +5,8 @@ import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
|
|
|
5
5
|
export const registerTool = (server) => {
|
|
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);
|
|
10
10
|
try {
|
|
11
11
|
const response = await client
|
|
12
12
|
.getTaxonomy()
|
|
@@ -5,8 +5,8 @@ import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
|
|
|
5
5
|
export const registerTool = (server) => {
|
|
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);
|
|
10
10
|
try {
|
|
11
11
|
const response = await client
|
|
12
12
|
.viewContentType()
|
|
@@ -5,8 +5,8 @@ import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
|
|
|
5
5
|
export const registerTool = (server) => {
|
|
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);
|
|
10
10
|
try {
|
|
11
11
|
const response = await client
|
|
12
12
|
.viewContentTypeSnippet()
|
|
@@ -8,8 +8,8 @@ export const registerTool = (server) => {
|
|
|
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);
|
|
13
13
|
try {
|
|
14
14
|
const response = await client
|
|
15
15
|
.viewLanguageVariant()
|
|
@@ -2,8 +2,8 @@ import { createMapiClient } from "../clients/kontentClients.js";
|
|
|
2
2
|
import { handleMcpToolError } from "../utils/errorHandler.js";
|
|
3
3
|
import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
|
|
4
4
|
export const registerTool = (server) => {
|
|
5
|
-
server.tool("list-assets-mapi", "Get all Kontent.ai assets from Management API", {}, async () => {
|
|
6
|
-
const client = createMapiClient();
|
|
5
|
+
server.tool("list-assets-mapi", "Get all Kontent.ai assets from Management API", {}, async (_, { authInfo: { token, clientId } = {} }) => {
|
|
6
|
+
const client = createMapiClient(clientId, token);
|
|
7
7
|
try {
|
|
8
8
|
const response = await client.listAssets().toAllPromise();
|
|
9
9
|
return createMcpToolSuccessResponse(response.data);
|
|
@@ -2,8 +2,8 @@ import { createMapiClient } from "../clients/kontentClients.js";
|
|
|
2
2
|
import { handleMcpToolError } from "../utils/errorHandler.js";
|
|
3
3
|
import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
|
|
4
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();
|
|
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);
|
|
7
7
|
try {
|
|
8
8
|
const response = await client.listContentTypeSnippets().toAllPromise();
|
|
9
9
|
return createMcpToolSuccessResponse(response.data);
|
|
@@ -2,8 +2,8 @@ import { createMapiClient } from "../clients/kontentClients.js";
|
|
|
2
2
|
import { handleMcpToolError } from "../utils/errorHandler.js";
|
|
3
3
|
import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
|
|
4
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();
|
|
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);
|
|
7
7
|
try {
|
|
8
8
|
const response = await client.listContentTypes().toAllPromise();
|
|
9
9
|
return createMcpToolSuccessResponse(response.data);
|
|
@@ -2,8 +2,8 @@ import { createMapiClient } from "../clients/kontentClients.js";
|
|
|
2
2
|
import { handleMcpToolError } from "../utils/errorHandler.js";
|
|
3
3
|
import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
|
|
4
4
|
export const registerTool = (server) => {
|
|
5
|
-
server.tool("list-languages-mapi", "Get all Kontent.ai languages from Management API", {}, async () => {
|
|
6
|
-
const client = createMapiClient();
|
|
5
|
+
server.tool("list-languages-mapi", "Get all Kontent.ai languages from Management API", {}, async (_, { authInfo: { token, clientId } = {} }) => {
|
|
6
|
+
const client = createMapiClient(clientId, token);
|
|
7
7
|
try {
|
|
8
8
|
const response = await client.listLanguages().toAllPromise();
|
|
9
9
|
return createMcpToolSuccessResponse(response.data);
|
|
@@ -2,8 +2,8 @@ import { createMapiClient } from "../clients/kontentClients.js";
|
|
|
2
2
|
import { handleMcpToolError } from "../utils/errorHandler.js";
|
|
3
3
|
import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
|
|
4
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();
|
|
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);
|
|
7
7
|
try {
|
|
8
8
|
const response = await client.listTaxonomies().toAllPromise();
|
|
9
9
|
return createMcpToolSuccessResponse(response.data);
|
|
@@ -2,8 +2,8 @@ import { createMapiClient } from "../clients/kontentClients.js";
|
|
|
2
2
|
import { handleMcpToolError } from "../utils/errorHandler.js";
|
|
3
3
|
import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
|
|
4
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();
|
|
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);
|
|
7
7
|
try {
|
|
8
8
|
const response = await client.listWorkflows().toPromise();
|
|
9
9
|
return createMcpToolSuccessResponse(response.data);
|
|
@@ -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);
|
|
59
59
|
try {
|
|
60
60
|
// Apply patch operations using the modifyContentType method
|
|
61
61
|
const response = await client
|
|
@@ -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);
|
|
26
26
|
try {
|
|
27
27
|
// Validate that displayTimezone can only be used with scheduledTo
|
|
28
28
|
if (displayTimezone && !scheduledTo) {
|
|
@@ -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);
|
|
26
26
|
try {
|
|
27
27
|
// Validate that displayTimezone can only be used with scheduledTo
|
|
28
28
|
if (displayTimezone && !scheduledTo) {
|
|
@@ -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);
|
|
24
24
|
try {
|
|
25
25
|
// First, verify the item exists by trying to get it
|
|
26
26
|
await client.viewContentItem().byItemId(id).toPromise();
|
|
@@ -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);
|
|
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
|
|
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
|
|
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
|
+
};
|
package/package.json
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kontent-ai/mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.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"
|