@kontent-ai/mcp-server 0.21.11 → 0.23.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 +39 -25
- package/build/schemas/collectionSchemas.js +37 -0
- package/build/schemas/filterVariantSchemas.js +1 -1
- package/build/schemas/languageSchemas.js +43 -0
- package/build/schemas/listSchemas.js +16 -0
- package/build/schemas/searchOperationSchemas.js +1 -4
- package/build/schemas/taxonomySchemas.js +5 -2
- package/build/schemas/workflowSchemas.js +6 -14
- package/build/server.js +26 -8
- package/build/telemetry/applicationInsights.js +1 -1
- package/build/test/utils/responseHelper.spec.js +149 -15
- package/build/tools/add-language-mapi.js +25 -0
- package/build/tools/change-variant-workflow-step-mapi.js +3 -4
- package/build/tools/create-variant-version-mapi.js +1 -2
- package/build/tools/get-asset-mapi.js +1 -1
- package/build/tools/get-item-mapi.js +1 -1
- package/build/tools/{get-variant-mapi.js → get-latest-variant-mapi.js} +2 -2
- package/build/tools/get-patch-guide.js +12 -0
- package/build/tools/get-published-variant-mapi.js +24 -0
- package/build/tools/get-taxonomy-group-mapi.js +1 -1
- package/build/tools/get-type-mapi.js +1 -1
- package/build/tools/get-type-snippet-mapi.js +1 -1
- package/build/tools/list-assets-mapi.js +1 -1
- package/build/tools/list-collections-mapi.js +15 -0
- package/build/tools/list-content-type-snippets-mapi.js +1 -1
- package/build/tools/list-content-types-mapi.js +1 -1
- package/build/tools/list-languages-mapi.js +1 -1
- package/build/tools/list-spaces-mapi.js +15 -0
- package/build/tools/list-taxonomy-groups-mapi.js +1 -1
- package/build/tools/list-variants-collection-mapi.js +26 -0
- package/build/tools/list-variants-components-type-mapi.js +26 -0
- package/build/tools/list-variants-item-mapi.js +21 -0
- package/build/tools/list-variants-space-mapi.js +24 -0
- package/build/tools/list-variants-type-mapi.js +26 -0
- package/build/tools/list-workflows-mapi.js +1 -1
- package/build/tools/patch-collections-mapi.js +25 -0
- package/build/tools/patch-content-type-mapi.js +1 -1
- package/build/tools/patch-language-mapi.js +24 -0
- package/build/tools/publish-variant-mapi.js +3 -5
- package/build/tools/unpublish-variant-mapi.js +3 -5
- package/build/tools/upsert-language-variant-mapi.js +1 -1
- package/build/utils/responseHelper.js +39 -13
- package/package.json +19 -15
- package/build/tools/context/initial-context.js +0 -195
- package/build/tools/get-initial-context.js +0 -13
package/README.md
CHANGED
|
@@ -61,9 +61,9 @@ npx @kontent-ai/mcp-server@latest shttp
|
|
|
61
61
|
|
|
62
62
|
## 🛠️ Available Tools
|
|
63
63
|
|
|
64
|
-
###
|
|
64
|
+
### Patch Operations Guide
|
|
65
65
|
|
|
66
|
-
* **get-
|
|
66
|
+
* **get-patch-guide** – 🚨 **REQUIRED before any patch operation**. Get JSON Patch operations guide for Kontent.ai Management API
|
|
67
67
|
|
|
68
68
|
### Content Type Management
|
|
69
69
|
|
|
@@ -89,11 +89,17 @@ npx @kontent-ai/mcp-server@latest shttp
|
|
|
89
89
|
|
|
90
90
|
* **get-item-mapi** – Get Kontent.ai item by internal ID from Management API
|
|
91
91
|
* **get-item-dapi** – Get Kontent.ai item by codename from Delivery API
|
|
92
|
-
* **get-variant-mapi** – Get Kontent.ai language variant
|
|
92
|
+
* **get-latest-variant-mapi** – Get latest version of Kontent.ai language variant from Management API
|
|
93
|
+
* **get-published-variant-mapi** – Get published version of Kontent.ai language variant from Management API
|
|
94
|
+
* **list-variants-item-mapi** – List all Kontent.ai language variants of a content item from Management API
|
|
95
|
+
* **list-variants-collection-mapi** – List Kontent.ai language variants by collection from Management API (paginated)
|
|
96
|
+
* **list-variants-type-mapi** – List Kontent.ai language variants by content type from Management API (paginated)
|
|
97
|
+
* **list-variants-components-type-mapi** – List Kontent.ai language variants containing components of a specific content type from Management API (paginated)
|
|
98
|
+
* **list-variants-space-mapi** – List Kontent.ai language variants by space from Management API (paginated)
|
|
93
99
|
* **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
|
|
94
100
|
* **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
|
|
95
101
|
* **delete-content-item-mapi** – Delete Kontent.ai content item by internal ID from Management API
|
|
96
|
-
* **upsert-language-variant-mapi** – Create or update Kontent.ai language variant of a content item via Management API.
|
|
102
|
+
* **upsert-language-variant-mapi** – Create or update Kontent.ai language variant of a content item via Management API. Element values must fulfill limitations and guidelines defined in content type. When updating, only provided elements will be modified
|
|
97
103
|
* **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
|
|
98
104
|
* **delete-language-variant-mapi** – Delete Kontent.ai language variant from Management API
|
|
99
105
|
* **filter-variants-mapi** – Filter Kontent.ai language variants of content items using Management API. Use for exact keyword matching and finding specific terms in content. Supports full filtering capabilities (content types, workflow steps, taxonomies, etc.) and optionally includes full content of variants. Returns paginated results with continuation token for fetching subsequent pages
|
|
@@ -107,6 +113,17 @@ npx @kontent-ai/mcp-server@latest shttp
|
|
|
107
113
|
### Language Management
|
|
108
114
|
|
|
109
115
|
* **list-languages-mapi** – Get all Kontent.ai languages from Management API
|
|
116
|
+
* **add-language-mapi** – Add new Kontent.ai language via Management API
|
|
117
|
+
* **patch-language-mapi** – Update Kontent.ai language using replace operations via Management API
|
|
118
|
+
|
|
119
|
+
### Collection Management
|
|
120
|
+
|
|
121
|
+
* **list-collections-mapi** – Get all Kontent.ai collections from Management API. Collections set boundaries for content items in your environment and help organize content by team, brand, or project
|
|
122
|
+
* **patch-collections-mapi** – Update Kontent.ai collections using patch operations (addInto to add new collections, move to reorder, remove to delete empty collections, replace to rename)
|
|
123
|
+
|
|
124
|
+
### Space Management
|
|
125
|
+
|
|
126
|
+
* **list-spaces-mapi** – Get all Kontent.ai spaces from Management API
|
|
110
127
|
|
|
111
128
|
### Workflow Management
|
|
112
129
|
|
|
@@ -217,7 +234,8 @@ Then configure your MCP client:
|
|
|
217
234
|
|
|
218
235
|
No environment variables required. The server accepts requests for multiple environments using URL path parameters and Bearer authentication.
|
|
219
236
|
|
|
220
|
-
|
|
237
|
+
<details>
|
|
238
|
+
<summary><strong>VS Code</strong></summary>
|
|
221
239
|
|
|
222
240
|
Create a `.vscode/mcp.json` file in your workspace:
|
|
223
241
|
|
|
@@ -261,7 +279,10 @@ For secure configuration with input prompts:
|
|
|
261
279
|
}
|
|
262
280
|
```
|
|
263
281
|
|
|
264
|
-
|
|
282
|
+
</details>
|
|
283
|
+
|
|
284
|
+
<details>
|
|
285
|
+
<summary><strong>Claude Desktop</strong></summary>
|
|
265
286
|
|
|
266
287
|
Update your Claude Desktop configuration file:
|
|
267
288
|
- **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
@@ -286,32 +307,25 @@ Use `mcp-remote` as a proxy to add authentication headers:
|
|
|
286
307
|
}
|
|
287
308
|
```
|
|
288
309
|
|
|
289
|
-
|
|
310
|
+
</details>
|
|
311
|
+
|
|
312
|
+
<details>
|
|
313
|
+
<summary><strong>Claude Code</strong></summary>
|
|
290
314
|
|
|
291
|
-
|
|
315
|
+
Add the server using the CLI:
|
|
292
316
|
|
|
293
317
|
```bash
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
--
|
|
297
|
-
--header "Authorization: Bearer <management-api-key>" \
|
|
298
|
-
kontent-ai-multi
|
|
318
|
+
claude mcp add --transport http kontent-ai-multi \
|
|
319
|
+
"http://localhost:3001/<environment-id>/mcp" \
|
|
320
|
+
--header "Authorization: Bearer <management-api-key>"
|
|
299
321
|
```
|
|
300
322
|
|
|
301
|
-
|
|
323
|
+
> **Note**: You can also configure this in your Claude Code settings JSON with the `url` and `headers` properties.
|
|
302
324
|
|
|
303
|
-
|
|
304
|
-
{
|
|
305
|
-
"kontent-ai-multi": {
|
|
306
|
-
"url": "http://localhost:3001/<environment-id>/mcp",
|
|
307
|
-
"headers": {
|
|
308
|
-
"Authorization": "Bearer <management-api-key>"
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
```
|
|
325
|
+
</details>
|
|
313
326
|
|
|
314
|
-
|
|
327
|
+
> [!IMPORTANT]
|
|
328
|
+
> Replace `<environment-id>` with your Kontent.ai environment ID (GUID) and `<management-api-key>` with your Management API key.
|
|
315
329
|
|
|
316
330
|
## 💻 Development
|
|
317
331
|
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { referenceObjectSchema } from "./referenceObjectSchema.js";
|
|
3
|
+
const addIntoOperationSchema = z.object({
|
|
4
|
+
op: z.literal("addInto"),
|
|
5
|
+
value: z.object({
|
|
6
|
+
name: z.string(),
|
|
7
|
+
codename: z.string().optional(),
|
|
8
|
+
external_id: z.string().optional(),
|
|
9
|
+
}),
|
|
10
|
+
before: referenceObjectSchema.optional(),
|
|
11
|
+
after: referenceObjectSchema.optional(),
|
|
12
|
+
});
|
|
13
|
+
const moveOperationSchema = z.object({
|
|
14
|
+
op: z.literal("move"),
|
|
15
|
+
reference: referenceObjectSchema,
|
|
16
|
+
before: referenceObjectSchema.optional(),
|
|
17
|
+
after: referenceObjectSchema.optional(),
|
|
18
|
+
});
|
|
19
|
+
const removeOperationSchema = z.object({
|
|
20
|
+
op: z.literal("remove"),
|
|
21
|
+
reference: referenceObjectSchema,
|
|
22
|
+
});
|
|
23
|
+
const replaceOperationSchema = z.object({
|
|
24
|
+
op: z.literal("replace"),
|
|
25
|
+
reference: referenceObjectSchema,
|
|
26
|
+
property_name: z.enum(["name"]),
|
|
27
|
+
value: z.string(),
|
|
28
|
+
});
|
|
29
|
+
export const collectionPatchOperationSchema = z.discriminatedUnion("op", [
|
|
30
|
+
addIntoOperationSchema,
|
|
31
|
+
moveOperationSchema,
|
|
32
|
+
removeOperationSchema,
|
|
33
|
+
replaceOperationSchema,
|
|
34
|
+
]);
|
|
35
|
+
export const collectionPatchOperationsSchema = z
|
|
36
|
+
.array(collectionPatchOperationSchema)
|
|
37
|
+
.min(1);
|
|
@@ -10,7 +10,7 @@ const userReferenceSchema = z
|
|
|
10
10
|
}),
|
|
11
11
|
z.object({
|
|
12
12
|
id: z.never().optional(),
|
|
13
|
-
email: z.
|
|
13
|
+
email: z.email().describe("User email address"),
|
|
14
14
|
}),
|
|
15
15
|
])
|
|
16
16
|
.describe("Reference to a user by either their id or email (but not both)");
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { referenceObjectSchema } from "./referenceObjectSchema.js";
|
|
3
|
+
export const addLanguageSchema = z.object({
|
|
4
|
+
name: z.string().describe("Display name of the language"),
|
|
5
|
+
codename: z.string().describe("Codename identifier for the language"),
|
|
6
|
+
is_active: z
|
|
7
|
+
.boolean()
|
|
8
|
+
.optional()
|
|
9
|
+
.describe("Whether the language is active (defaults to true)"),
|
|
10
|
+
fallback_language: referenceObjectSchema
|
|
11
|
+
.optional()
|
|
12
|
+
.describe("Reference to fallback language (by id, codename, or external_id)"),
|
|
13
|
+
external_id: z.string().optional().describe("External ID for the language"),
|
|
14
|
+
});
|
|
15
|
+
const languageReplaceOperationSchema = z.discriminatedUnion("property_name", [
|
|
16
|
+
z.object({
|
|
17
|
+
op: z.literal("replace"),
|
|
18
|
+
property_name: z.literal("codename"),
|
|
19
|
+
value: z.string(),
|
|
20
|
+
}),
|
|
21
|
+
z.object({
|
|
22
|
+
op: z.literal("replace"),
|
|
23
|
+
property_name: z.literal("name"),
|
|
24
|
+
value: z.string(),
|
|
25
|
+
}),
|
|
26
|
+
z.object({
|
|
27
|
+
op: z.literal("replace"),
|
|
28
|
+
property_name: z.literal("is_active"),
|
|
29
|
+
value: z.boolean(),
|
|
30
|
+
}),
|
|
31
|
+
z.object({
|
|
32
|
+
op: z.literal("replace"),
|
|
33
|
+
property_name: z.literal("fallback_language"),
|
|
34
|
+
value: referenceObjectSchema,
|
|
35
|
+
}),
|
|
36
|
+
]);
|
|
37
|
+
export const patchLanguageSchema = z.object({
|
|
38
|
+
languageId: z.string().describe("Language ID to modify"),
|
|
39
|
+
operations: z
|
|
40
|
+
.array(languageReplaceOperationSchema)
|
|
41
|
+
.min(1)
|
|
42
|
+
.describe("Array of replace operations for codename, name, is_active, or fallback_language. Note: Only active languages can be modified - if language is deactivated, is_active: true must be first operation."),
|
|
43
|
+
});
|
|
@@ -24,3 +24,19 @@ export const listContentTypeSnippetsSchema = z.object({
|
|
|
24
24
|
export const listAssetsSchema = z.object({
|
|
25
25
|
continuation_token: continuationTokenField,
|
|
26
26
|
});
|
|
27
|
+
export const listVariantsCollectionSchema = z.object({
|
|
28
|
+
collectionId: z.string().describe("Collection ID"),
|
|
29
|
+
continuation_token: continuationTokenField,
|
|
30
|
+
});
|
|
31
|
+
export const listVariantsTypeSchema = z.object({
|
|
32
|
+
contentTypeId: z.string().describe("Content type ID"),
|
|
33
|
+
continuation_token: continuationTokenField,
|
|
34
|
+
});
|
|
35
|
+
export const listVariantsComponentsTypeSchema = z.object({
|
|
36
|
+
contentTypeId: z.string().describe("Content type ID"),
|
|
37
|
+
continuation_token: continuationTokenField,
|
|
38
|
+
});
|
|
39
|
+
export const listVariantsSpaceSchema = z.object({
|
|
40
|
+
spaceId: z.string().describe("Space ID"),
|
|
41
|
+
continuation_token: continuationTokenField,
|
|
42
|
+
});
|
|
@@ -5,10 +5,7 @@ export const searchOperationSchema = z.object({
|
|
|
5
5
|
.describe("Search phrase for AI-powered semantic search. Uses vector database to find content by meaning and similarity, not just exact keyword matching"),
|
|
6
6
|
filter: z
|
|
7
7
|
.object({
|
|
8
|
-
variantId: z
|
|
9
|
-
.string()
|
|
10
|
-
.uuid()
|
|
11
|
-
.describe("UUID of the language variant to filter by"),
|
|
8
|
+
variantId: z.uuid().describe("UUID of the language variant to filter by"),
|
|
12
9
|
})
|
|
13
10
|
.describe("Mandatory content item variant filter to restrict search scope. Must specify a valid variant UUID"),
|
|
14
11
|
});
|
|
@@ -4,7 +4,7 @@ const taxonomyTermSchema = z.object({
|
|
|
4
4
|
name: z.string(),
|
|
5
5
|
codename: z.string().optional(),
|
|
6
6
|
external_id: z.string().optional(),
|
|
7
|
-
terms: z.lazy(() => z.array(taxonomyTermSchema)),
|
|
7
|
+
terms: z.lazy(() => z.array(taxonomyTermSchema)).optional(),
|
|
8
8
|
});
|
|
9
9
|
// Schema for a taxonomy group
|
|
10
10
|
export const taxonomyGroupSchemas = {
|
|
@@ -14,5 +14,8 @@ export const taxonomyGroupSchemas = {
|
|
|
14
14
|
.optional()
|
|
15
15
|
.describe("Codename (auto-generated if omitted)"),
|
|
16
16
|
external_id: z.string().optional().describe("External ID"),
|
|
17
|
-
terms: z
|
|
17
|
+
terms: z
|
|
18
|
+
.array(taxonomyTermSchema)
|
|
19
|
+
.optional()
|
|
20
|
+
.describe("Taxonomy terms hierarchy"),
|
|
18
21
|
};
|
|
@@ -2,7 +2,6 @@ import { z } from "zod";
|
|
|
2
2
|
// Schema for a workflow step
|
|
3
3
|
const workflowStepSchema = z.object({
|
|
4
4
|
id: z
|
|
5
|
-
.string()
|
|
6
5
|
.uuid()
|
|
7
6
|
.describe("The unique identifier of the workflow step in UUID format"),
|
|
8
7
|
name: z.string().describe("The human-readable name of the workflow step"),
|
|
@@ -10,18 +9,17 @@ const workflowStepSchema = z.object({
|
|
|
10
9
|
.string()
|
|
11
10
|
.describe("The codename of the workflow step used for API operations"),
|
|
12
11
|
transitions_to: z
|
|
13
|
-
.array(z.
|
|
12
|
+
.array(z.uuid())
|
|
14
13
|
.describe("Array of workflow step IDs that this step can transition to")
|
|
15
14
|
.optional(),
|
|
16
15
|
role_ids: z
|
|
17
|
-
.array(z.
|
|
16
|
+
.array(z.uuid())
|
|
18
17
|
.describe("Array of role IDs that have permissions for this workflow step")
|
|
19
18
|
.optional(),
|
|
20
19
|
});
|
|
21
20
|
// Schema for the published step
|
|
22
21
|
const publishedStepSchema = z.object({
|
|
23
22
|
id: z
|
|
24
|
-
.string()
|
|
25
23
|
.uuid()
|
|
26
24
|
.describe("The unique identifier of the published step in UUID format"),
|
|
27
25
|
name: z
|
|
@@ -31,18 +29,17 @@ const publishedStepSchema = z.object({
|
|
|
31
29
|
.string()
|
|
32
30
|
.describe("The codename of the published step - typically 'published'"),
|
|
33
31
|
unpublish_role_ids: z
|
|
34
|
-
.array(z.
|
|
32
|
+
.array(z.uuid())
|
|
35
33
|
.describe("Array of role IDs that can unpublish content from this step")
|
|
36
34
|
.optional(),
|
|
37
35
|
create_new_version_role_ids: z
|
|
38
|
-
.array(z.
|
|
36
|
+
.array(z.uuid())
|
|
39
37
|
.describe("Array of role IDs that can create new versions of content in this step")
|
|
40
38
|
.optional(),
|
|
41
39
|
});
|
|
42
40
|
// Schema for the scheduled step
|
|
43
41
|
const scheduledStepSchema = z.object({
|
|
44
42
|
id: z
|
|
45
|
-
.string()
|
|
46
43
|
.uuid()
|
|
47
44
|
.describe("The unique identifier of the scheduled step in UUID format"),
|
|
48
45
|
name: z
|
|
@@ -55,7 +52,6 @@ const scheduledStepSchema = z.object({
|
|
|
55
52
|
// Schema for the archived step
|
|
56
53
|
const archivedStepSchema = z.object({
|
|
57
54
|
id: z
|
|
58
|
-
.string()
|
|
59
55
|
.uuid()
|
|
60
56
|
.describe("The unique identifier of the archived step in UUID format"),
|
|
61
57
|
name: z
|
|
@@ -65,7 +61,7 @@ const archivedStepSchema = z.object({
|
|
|
65
61
|
.string()
|
|
66
62
|
.describe("The codename of the archived step - typically 'archived'"),
|
|
67
63
|
role_ids: z
|
|
68
|
-
.array(z.
|
|
64
|
+
.array(z.uuid())
|
|
69
65
|
.describe("Array of role IDs that can unarchive content from this step")
|
|
70
66
|
.optional(),
|
|
71
67
|
});
|
|
@@ -74,7 +70,6 @@ const workflowScopeSchema = z.object({
|
|
|
74
70
|
content_types: z
|
|
75
71
|
.array(z.object({
|
|
76
72
|
id: z
|
|
77
|
-
.string()
|
|
78
73
|
.uuid()
|
|
79
74
|
.describe("The unique identifier of the content type in UUID format"),
|
|
80
75
|
}))
|
|
@@ -82,10 +77,7 @@ const workflowScopeSchema = z.object({
|
|
|
82
77
|
});
|
|
83
78
|
// Main workflow schema
|
|
84
79
|
export const workflowSchema = z.object({
|
|
85
|
-
id: z
|
|
86
|
-
.string()
|
|
87
|
-
.uuid()
|
|
88
|
-
.describe("The unique identifier of the workflow in UUID format"),
|
|
80
|
+
id: z.uuid().describe("The unique identifier of the workflow in UUID format"),
|
|
89
81
|
name: z.string().describe("The human-readable name of the workflow"),
|
|
90
82
|
codename: z
|
|
91
83
|
.string()
|
package/build/server.js
CHANGED
|
@@ -3,6 +3,7 @@ import packageJson from "../package.json" with { type: "json" };
|
|
|
3
3
|
import { registerTool as registerAddContentItemMapi } from "./tools/add-content-item-mapi.js";
|
|
4
4
|
import { registerTool as registerAddContentTypeMapi } from "./tools/add-content-type-mapi.js";
|
|
5
5
|
import { registerTool as registerAddContentTypeSnippetMapi } from "./tools/add-content-type-snippet-mapi.js";
|
|
6
|
+
import { registerTool as registerAddLanguageMapi } from "./tools/add-language-mapi.js";
|
|
6
7
|
import { registerTool as registerAddTaxonomyGroupMapi } from "./tools/add-taxonomy-group-mapi.js";
|
|
7
8
|
import { registerTool as registerChangeVariantWorkflowStepMapi } from "./tools/change-variant-workflow-step-mapi.js";
|
|
8
9
|
import { registerTool as registerCreateVariantVersionMapi } from "./tools/create-variant-version-mapi.js";
|
|
@@ -11,19 +12,29 @@ import { registerTool as registerDeleteContentTypeMapi } from "./tools/delete-co
|
|
|
11
12
|
import { registerTool as registerDeleteLanguageVariantMapi } from "./tools/delete-language-variant-mapi.js";
|
|
12
13
|
import { registerTool as registerFilterVariantsMapi } from "./tools/filter-variants-mapi.js";
|
|
13
14
|
import { registerTool as registerGetAssetMapi } from "./tools/get-asset-mapi.js";
|
|
14
|
-
import { registerTool as registerGetInitialContext } from "./tools/get-initial-context.js";
|
|
15
15
|
import { registerTool as registerGetItemMapi } from "./tools/get-item-mapi.js";
|
|
16
|
+
import { registerTool as registerGetLatestVariantMapi } from "./tools/get-latest-variant-mapi.js";
|
|
17
|
+
import { registerTool as registerGetPatchGuide } from "./tools/get-patch-guide.js";
|
|
18
|
+
import { registerTool as registerGetPublishedVariantMapi } from "./tools/get-published-variant-mapi.js";
|
|
16
19
|
import { registerTool as registerGetTaxonomyGroupMapi } from "./tools/get-taxonomy-group-mapi.js";
|
|
17
20
|
import { registerTool as registerGetTypeMapi } from "./tools/get-type-mapi.js";
|
|
18
21
|
import { registerTool as registerGetTypeSnippetMapi } from "./tools/get-type-snippet-mapi.js";
|
|
19
|
-
import { registerTool as registerGetVariantMapi } from "./tools/get-variant-mapi.js";
|
|
20
22
|
import { registerTool as registerListAssetsMapi } from "./tools/list-assets-mapi.js";
|
|
23
|
+
import { registerTool as registerListCollectionsMapi } from "./tools/list-collections-mapi.js";
|
|
21
24
|
import { registerTool as registerListContentTypeSnippetsMapi } from "./tools/list-content-type-snippets-mapi.js";
|
|
22
25
|
import { registerTool as registerListContentTypesMapi } from "./tools/list-content-types-mapi.js";
|
|
23
26
|
import { registerTool as registerListLanguagesMapi } from "./tools/list-languages-mapi.js";
|
|
27
|
+
import { registerTool as registerListSpacesMapi } from "./tools/list-spaces-mapi.js";
|
|
24
28
|
import { registerTool as registerListTaxonomyGroupsMapi } from "./tools/list-taxonomy-groups-mapi.js";
|
|
29
|
+
import { registerTool as registerListVariantsCollectionMapi } from "./tools/list-variants-collection-mapi.js";
|
|
30
|
+
import { registerTool as registerListVariantsComponentsTypeMapi } from "./tools/list-variants-components-type-mapi.js";
|
|
31
|
+
import { registerTool as registerListVariantsItemMapi } from "./tools/list-variants-item-mapi.js";
|
|
32
|
+
import { registerTool as registerListVariantsSpaceMapi } from "./tools/list-variants-space-mapi.js";
|
|
33
|
+
import { registerTool as registerListVariantsTypeMapi } from "./tools/list-variants-type-mapi.js";
|
|
25
34
|
import { registerTool as registerListWorkflowsMapi } from "./tools/list-workflows-mapi.js";
|
|
35
|
+
import { registerTool as registerPatchCollectionsMapi } from "./tools/patch-collections-mapi.js";
|
|
26
36
|
import { registerTool as registerPatchContentTypeMapi } from "./tools/patch-content-type-mapi.js";
|
|
37
|
+
import { registerTool as registerPatchLanguageMapi } from "./tools/patch-language-mapi.js";
|
|
27
38
|
import { registerTool as registerPublishVariantMapi } from "./tools/publish-variant-mapi.js";
|
|
28
39
|
import { registerTool as registerSearchVariantsMapi } from "./tools/search-variants-mapi.js";
|
|
29
40
|
import { registerTool as registerUnpublishVariantMapi } from "./tools/unpublish-variant-mapi.js";
|
|
@@ -34,19 +45,26 @@ export const createServer = () => {
|
|
|
34
45
|
const server = new McpServer({
|
|
35
46
|
name: "kontent-ai",
|
|
36
47
|
version: packageJson.version,
|
|
37
|
-
capabilities: {
|
|
38
|
-
resources: {},
|
|
39
|
-
tools: {},
|
|
40
|
-
},
|
|
41
48
|
});
|
|
42
49
|
// Register all tools
|
|
43
|
-
|
|
50
|
+
registerGetPatchGuide(server);
|
|
44
51
|
registerGetItemMapi(server);
|
|
45
|
-
|
|
52
|
+
registerGetLatestVariantMapi(server);
|
|
53
|
+
registerGetPublishedVariantMapi(server);
|
|
54
|
+
registerListVariantsItemMapi(server);
|
|
55
|
+
registerListVariantsCollectionMapi(server);
|
|
56
|
+
registerListVariantsTypeMapi(server);
|
|
57
|
+
registerListVariantsComponentsTypeMapi(server);
|
|
58
|
+
registerListVariantsSpaceMapi(server);
|
|
46
59
|
registerGetTypeMapi(server);
|
|
47
60
|
registerListContentTypesMapi(server);
|
|
48
61
|
registerDeleteContentTypeMapi(server);
|
|
49
62
|
registerListLanguagesMapi(server);
|
|
63
|
+
registerAddLanguageMapi(server);
|
|
64
|
+
registerPatchLanguageMapi(server);
|
|
65
|
+
registerListCollectionsMapi(server);
|
|
66
|
+
registerPatchCollectionsMapi(server);
|
|
67
|
+
registerListSpacesMapi(server);
|
|
50
68
|
registerGetAssetMapi(server);
|
|
51
69
|
registerListAssetsMapi(server);
|
|
52
70
|
registerAddContentTypeMapi(server);
|
|
@@ -148,7 +148,7 @@ export function initializeApplicationInsights() {
|
|
|
148
148
|
.setAutoCollectDependencies(false)
|
|
149
149
|
.setAutoDependencyCorrelation(false)
|
|
150
150
|
.setAutoCollectHeartbeat(false)
|
|
151
|
-
.setAutoCollectPerformance(false)
|
|
151
|
+
.setAutoCollectPerformance(false, false)
|
|
152
152
|
.setAutoCollectIncomingRequestAzureFunctions(false)
|
|
153
153
|
.setAutoCollectPreAggregatedMetrics(false)
|
|
154
154
|
.start();
|
|
@@ -53,18 +53,18 @@ describe("isEmptyOrDefault", () => {
|
|
|
53
53
|
});
|
|
54
54
|
});
|
|
55
55
|
describe("removeEmptyValues", () => {
|
|
56
|
-
describe("primitive values", () => {
|
|
57
|
-
it("returns
|
|
58
|
-
assert.strictEqual(removeEmptyValues(null),
|
|
56
|
+
describe("primitive values at root level - preserved as-is", () => {
|
|
57
|
+
it("returns null for null", () => {
|
|
58
|
+
assert.strictEqual(removeEmptyValues(null), null);
|
|
59
59
|
});
|
|
60
60
|
it("returns undefined for undefined", () => {
|
|
61
61
|
assert.strictEqual(removeEmptyValues(undefined), undefined);
|
|
62
62
|
});
|
|
63
|
-
it("
|
|
64
|
-
assert.strictEqual(removeEmptyValues(""),
|
|
63
|
+
it("preserves empty string at root level", () => {
|
|
64
|
+
assert.strictEqual(removeEmptyValues(""), "");
|
|
65
65
|
});
|
|
66
|
-
it("
|
|
67
|
-
assert.strictEqual(removeEmptyValues("<p><br/></p>"),
|
|
66
|
+
it("preserves rich text empty paragraph at root level", () => {
|
|
67
|
+
assert.strictEqual(removeEmptyValues("<p><br/></p>"), "<p><br/></p>");
|
|
68
68
|
});
|
|
69
69
|
it("preserves non-empty string", () => {
|
|
70
70
|
assert.strictEqual(removeEmptyValues("hello"), "hello");
|
|
@@ -77,14 +77,14 @@ describe("removeEmptyValues", () => {
|
|
|
77
77
|
});
|
|
78
78
|
});
|
|
79
79
|
describe("arrays", () => {
|
|
80
|
-
it("returns
|
|
81
|
-
assert.
|
|
80
|
+
it("returns empty array for empty array at root level", () => {
|
|
81
|
+
assert.deepStrictEqual(removeEmptyValues([]), []);
|
|
82
82
|
});
|
|
83
83
|
it("removes empty values from array", () => {
|
|
84
84
|
assert.deepStrictEqual(removeEmptyValues([1, null, 2, "", 3]), [1, 2, 3]);
|
|
85
85
|
});
|
|
86
|
-
it("returns
|
|
87
|
-
assert.
|
|
86
|
+
it("returns empty array when all array items are empty at root level", () => {
|
|
87
|
+
assert.deepStrictEqual(removeEmptyValues([null, "", [], {}]), []);
|
|
88
88
|
});
|
|
89
89
|
it("recursively cleans nested arrays", () => {
|
|
90
90
|
assert.deepStrictEqual(removeEmptyValues([1, [2, null, 3], [null, ""]]), [
|
|
@@ -94,8 +94,8 @@ describe("removeEmptyValues", () => {
|
|
|
94
94
|
});
|
|
95
95
|
});
|
|
96
96
|
describe("objects", () => {
|
|
97
|
-
it("returns
|
|
98
|
-
assert.
|
|
97
|
+
it("returns empty object for empty object at root level", () => {
|
|
98
|
+
assert.deepStrictEqual(removeEmptyValues({}), {});
|
|
99
99
|
});
|
|
100
100
|
it("removes null properties", () => {
|
|
101
101
|
assert.deepStrictEqual(removeEmptyValues({ a: 1, b: null }), { a: 1 });
|
|
@@ -119,8 +119,8 @@ describe("removeEmptyValues", () => {
|
|
|
119
119
|
a: 1,
|
|
120
120
|
});
|
|
121
121
|
});
|
|
122
|
-
it("returns
|
|
123
|
-
assert.
|
|
122
|
+
it("returns empty object when all properties are empty at root level", () => {
|
|
123
|
+
assert.deepStrictEqual(removeEmptyValues({ a: null, b: "", c: [], d: {} }), {});
|
|
124
124
|
});
|
|
125
125
|
});
|
|
126
126
|
describe("nested structures", () => {
|
|
@@ -448,3 +448,137 @@ describe("variant with all empty elements", () => {
|
|
|
448
448
|
assert.deepStrictEqual(result, {}, "All empty elements should be removed, resulting in an empty object");
|
|
449
449
|
});
|
|
450
450
|
});
|
|
451
|
+
describe("top-level empty value preservation", () => {
|
|
452
|
+
describe("createMcpToolSuccessResponse", () => {
|
|
453
|
+
it("returns empty array when input is empty array", () => {
|
|
454
|
+
const response = createMcpToolSuccessResponse([]);
|
|
455
|
+
const parsed = JSON.parse(response.content[0].text);
|
|
456
|
+
assert.deepStrictEqual(parsed, []);
|
|
457
|
+
});
|
|
458
|
+
it("returns empty object when input is empty object", () => {
|
|
459
|
+
const response = createMcpToolSuccessResponse({});
|
|
460
|
+
const parsed = JSON.parse(response.content[0].text);
|
|
461
|
+
assert.deepStrictEqual(parsed, {});
|
|
462
|
+
});
|
|
463
|
+
it("returns empty object when all properties are removed", () => {
|
|
464
|
+
const input = { a: null, b: "", c: [] };
|
|
465
|
+
const response = createMcpToolSuccessResponse(input);
|
|
466
|
+
const parsed = JSON.parse(response.content[0].text);
|
|
467
|
+
assert.deepStrictEqual(parsed, {});
|
|
468
|
+
});
|
|
469
|
+
it("returns empty array when all array items are removed", () => {
|
|
470
|
+
const input = [null, "", [], {}];
|
|
471
|
+
const response = createMcpToolSuccessResponse(input);
|
|
472
|
+
const parsed = JSON.parse(response.content[0].text);
|
|
473
|
+
assert.deepStrictEqual(parsed, []);
|
|
474
|
+
});
|
|
475
|
+
it("returns valid JSON string (not undefined)", () => {
|
|
476
|
+
const response = createMcpToolSuccessResponse([]);
|
|
477
|
+
assert.strictEqual(typeof response.content[0].text, "string");
|
|
478
|
+
assert.doesNotThrow(() => JSON.parse(response.content[0].text));
|
|
479
|
+
});
|
|
480
|
+
it("handles filter-variants-like response with empty data array", () => {
|
|
481
|
+
const input = {
|
|
482
|
+
data: [],
|
|
483
|
+
pagination: {
|
|
484
|
+
continuation_token: null,
|
|
485
|
+
},
|
|
486
|
+
};
|
|
487
|
+
const response = createMcpToolSuccessResponse(input);
|
|
488
|
+
const parsed = JSON.parse(response.content[0].text);
|
|
489
|
+
assert.deepStrictEqual(parsed, {});
|
|
490
|
+
});
|
|
491
|
+
});
|
|
492
|
+
describe("createVariantMcpToolSuccessResponse", () => {
|
|
493
|
+
it("returns empty array when input is empty array", () => {
|
|
494
|
+
const response = createVariantMcpToolSuccessResponse([]);
|
|
495
|
+
const parsed = JSON.parse(response.content[0].text);
|
|
496
|
+
assert.deepStrictEqual(parsed, []);
|
|
497
|
+
});
|
|
498
|
+
it("returns empty object when input is empty object", () => {
|
|
499
|
+
const response = createVariantMcpToolSuccessResponse({});
|
|
500
|
+
const parsed = JSON.parse(response.content[0].text);
|
|
501
|
+
assert.deepStrictEqual(parsed, {});
|
|
502
|
+
});
|
|
503
|
+
it("returns empty object when all properties are removed", () => {
|
|
504
|
+
const input = { a: null, b: "", c: [] };
|
|
505
|
+
const response = createVariantMcpToolSuccessResponse(input);
|
|
506
|
+
const parsed = JSON.parse(response.content[0].text);
|
|
507
|
+
assert.deepStrictEqual(parsed, {});
|
|
508
|
+
});
|
|
509
|
+
it("returns empty array when all array items are removed", () => {
|
|
510
|
+
const input = [null, "", [], {}];
|
|
511
|
+
const response = createVariantMcpToolSuccessResponse(input);
|
|
512
|
+
const parsed = JSON.parse(response.content[0].text);
|
|
513
|
+
assert.deepStrictEqual(parsed, []);
|
|
514
|
+
});
|
|
515
|
+
it("returns valid JSON string (not undefined)", () => {
|
|
516
|
+
const response = createVariantMcpToolSuccessResponse([]);
|
|
517
|
+
assert.strictEqual(typeof response.content[0].text, "string");
|
|
518
|
+
assert.doesNotThrow(() => JSON.parse(response.content[0].text));
|
|
519
|
+
});
|
|
520
|
+
it("handles filter-variants-like response with empty data array", () => {
|
|
521
|
+
const input = {
|
|
522
|
+
data: [],
|
|
523
|
+
pagination: {
|
|
524
|
+
continuation_token: null,
|
|
525
|
+
},
|
|
526
|
+
};
|
|
527
|
+
const response = createVariantMcpToolSuccessResponse(input);
|
|
528
|
+
const parsed = JSON.parse(response.content[0].text);
|
|
529
|
+
assert.deepStrictEqual(parsed, {});
|
|
530
|
+
});
|
|
531
|
+
it("handles filter-variants response with variants array becoming empty", () => {
|
|
532
|
+
const input = {
|
|
533
|
+
variants: [
|
|
534
|
+
{
|
|
535
|
+
elements: [
|
|
536
|
+
{ element: { id: "el-1" }, value: null },
|
|
537
|
+
{ element: { id: "el-2" }, value: "" },
|
|
538
|
+
],
|
|
539
|
+
},
|
|
540
|
+
],
|
|
541
|
+
pagination: {
|
|
542
|
+
continuation_token: null,
|
|
543
|
+
},
|
|
544
|
+
};
|
|
545
|
+
const response = createVariantMcpToolSuccessResponse(input);
|
|
546
|
+
const parsed = JSON.parse(response.content[0].text);
|
|
547
|
+
// variants array should still be present (with empty variant objects)
|
|
548
|
+
assert.ok(parsed.variants !== undefined);
|
|
549
|
+
assert.strictEqual(parsed.variants.length, 1);
|
|
550
|
+
});
|
|
551
|
+
});
|
|
552
|
+
});
|
|
553
|
+
describe("undefined input handling - MCP protocol compliance", () => {
|
|
554
|
+
describe("createMcpToolSuccessResponse", () => {
|
|
555
|
+
it("returns string when input is undefined", () => {
|
|
556
|
+
const response = createMcpToolSuccessResponse(undefined);
|
|
557
|
+
assert.strictEqual(typeof response.content[0].text, "string");
|
|
558
|
+
});
|
|
559
|
+
it("returns 'undefined' text when input is undefined", () => {
|
|
560
|
+
const response = createMcpToolSuccessResponse(undefined);
|
|
561
|
+
assert.strictEqual(response.content[0].text, "undefined");
|
|
562
|
+
});
|
|
563
|
+
it("returns valid JSON string when input is null", () => {
|
|
564
|
+
const response = createMcpToolSuccessResponse(null);
|
|
565
|
+
assert.strictEqual(typeof response.content[0].text, "string");
|
|
566
|
+
assert.doesNotThrow(() => JSON.parse(response.content[0].text));
|
|
567
|
+
});
|
|
568
|
+
});
|
|
569
|
+
describe("createVariantMcpToolSuccessResponse", () => {
|
|
570
|
+
it("returns string when input is undefined", () => {
|
|
571
|
+
const response = createVariantMcpToolSuccessResponse(undefined);
|
|
572
|
+
assert.strictEqual(typeof response.content[0].text, "string");
|
|
573
|
+
});
|
|
574
|
+
it("returns 'undefined' text when input is undefined", () => {
|
|
575
|
+
const response = createVariantMcpToolSuccessResponse(undefined);
|
|
576
|
+
assert.strictEqual(response.content[0].text, "undefined");
|
|
577
|
+
});
|
|
578
|
+
it("returns valid JSON string when input is null", () => {
|
|
579
|
+
const response = createVariantMcpToolSuccessResponse(null);
|
|
580
|
+
assert.strictEqual(typeof response.content[0].text, "string");
|
|
581
|
+
assert.doesNotThrow(() => JSON.parse(response.content[0].text));
|
|
582
|
+
});
|
|
583
|
+
});
|
|
584
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { createMapiClient } from "../clients/kontentClients.js";
|
|
2
|
+
import { addLanguageSchema } from "../schemas/languageSchemas.js";
|
|
3
|
+
import { handleMcpToolError } from "../utils/errorHandler.js";
|
|
4
|
+
import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
|
|
5
|
+
export const registerTool = (server) => {
|
|
6
|
+
server.tool("add-language-mapi", "Add new Kontent.ai language via Management API", addLanguageSchema.shape, async ({ name, codename, is_active, fallback_language, external_id }, { authInfo: { token, clientId } = {} }) => {
|
|
7
|
+
const client = createMapiClient(clientId, token);
|
|
8
|
+
try {
|
|
9
|
+
const response = await client
|
|
10
|
+
.addLanguage()
|
|
11
|
+
.withData({
|
|
12
|
+
name,
|
|
13
|
+
codename,
|
|
14
|
+
is_active,
|
|
15
|
+
fallback_language,
|
|
16
|
+
external_id,
|
|
17
|
+
})
|
|
18
|
+
.toPromise();
|
|
19
|
+
return createMcpToolSuccessResponse(response.rawData);
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
return handleMcpToolError(error, "Language Creation");
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
};
|