@kontent-ai/mcp-server 0.21.11 → 0.22.1
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 +36 -22
- package/build/schemas/collectionSchemas.js +37 -0
- package/build/schemas/languageSchemas.js +43 -0
- package/build/schemas/listSchemas.js +16 -0
- package/build/schemas/taxonomySchemas.js +5 -2
- package/build/server.js +24 -2
- package/build/test/utils/responseHelper.spec.js +149 -15
- package/build/tools/add-language-mapi.js +25 -0
- package/build/tools/context/initial-context.js +6 -0
- package/build/tools/{get-variant-mapi.js → get-latest-variant-mapi.js} +2 -2
- package/build/tools/get-published-variant-mapi.js +24 -0
- package/build/tools/list-collections-mapi.js +15 -0
- package/build/tools/list-spaces-mapi.js +15 -0
- 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/patch-collections-mapi.js +25 -0
- package/build/tools/patch-language-mapi.js +24 -0
- package/build/utils/responseHelper.js +39 -13
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -89,7 +89,13 @@ 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
|
|
@@ -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);
|
|
@@ -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
|
+
});
|
|
@@ -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
|
};
|
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";
|
|
@@ -13,17 +14,27 @@ import { registerTool as registerFilterVariantsMapi } from "./tools/filter-varia
|
|
|
13
14
|
import { registerTool as registerGetAssetMapi } from "./tools/get-asset-mapi.js";
|
|
14
15
|
import { registerTool as registerGetInitialContext } from "./tools/get-initial-context.js";
|
|
15
16
|
import { registerTool as registerGetItemMapi } from "./tools/get-item-mapi.js";
|
|
17
|
+
import { registerTool as registerGetLatestVariantMapi } from "./tools/get-latest-variant-mapi.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";
|
|
@@ -42,11 +53,22 @@ export const createServer = () => {
|
|
|
42
53
|
// Register all tools
|
|
43
54
|
registerGetInitialContext(server);
|
|
44
55
|
registerGetItemMapi(server);
|
|
45
|
-
|
|
56
|
+
registerGetLatestVariantMapi(server);
|
|
57
|
+
registerGetPublishedVariantMapi(server);
|
|
58
|
+
registerListVariantsItemMapi(server);
|
|
59
|
+
registerListVariantsCollectionMapi(server);
|
|
60
|
+
registerListVariantsTypeMapi(server);
|
|
61
|
+
registerListVariantsComponentsTypeMapi(server);
|
|
62
|
+
registerListVariantsSpaceMapi(server);
|
|
46
63
|
registerGetTypeMapi(server);
|
|
47
64
|
registerListContentTypesMapi(server);
|
|
48
65
|
registerDeleteContentTypeMapi(server);
|
|
49
66
|
registerListLanguagesMapi(server);
|
|
67
|
+
registerAddLanguageMapi(server);
|
|
68
|
+
registerPatchLanguageMapi(server);
|
|
69
|
+
registerListCollectionsMapi(server);
|
|
70
|
+
registerPatchCollectionsMapi(server);
|
|
71
|
+
registerListSpacesMapi(server);
|
|
50
72
|
registerGetAssetMapi(server);
|
|
51
73
|
registerListAssetsMapi(server);
|
|
52
74
|
registerAddContentTypeMapi(server);
|
|
@@ -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
|
+
};
|
|
@@ -17,6 +17,12 @@ Content types define the structure and blueprint for language variants. They spe
|
|
|
17
17
|
### Content Type Snippets
|
|
18
18
|
Content type snippets are reusable field groups that promote consistency across multiple content types. Following the DRY principle (define once, use everywhere), one snippet can be used across multiple content types. This prevents duplication and ensures consistency when you need the same fields across different content types.
|
|
19
19
|
|
|
20
|
+
### Collections
|
|
21
|
+
Collections organize content items into logical groups by team, brand, or project. Each content item belongs to exactly one collection.
|
|
22
|
+
|
|
23
|
+
### Languages
|
|
24
|
+
Languages define available locales for content. Each language can have a fallback language for content inheritance and can be activated or deactivated.
|
|
25
|
+
|
|
20
26
|
## Understanding Key Relationships
|
|
21
27
|
|
|
22
28
|
The content structure flows from Content Type → Content Item → Language Variant(s). For reusability, Content Type Snippets can be included in multiple Content Types. For localization, each Content Item can have one Language Variant per language.
|
|
@@ -3,7 +3,7 @@ import { createMapiClient } from "../clients/kontentClients.js";
|
|
|
3
3
|
import { handleMcpToolError } from "../utils/errorHandler.js";
|
|
4
4
|
import { createVariantMcpToolSuccessResponse } from "../utils/responseHelper.js";
|
|
5
5
|
export const registerTool = (server) => {
|
|
6
|
-
server.tool("get-variant-mapi", "Get Kontent.ai variant", {
|
|
6
|
+
server.tool("get-latest-variant-mapi", "Get latest version of Kontent.ai language variant from Management API", {
|
|
7
7
|
itemId: z.string().describe("Item ID"),
|
|
8
8
|
languageId: z.string().describe("Language variant ID"),
|
|
9
9
|
}, async ({ itemId, languageId }, { authInfo: { token, clientId } = {} }) => {
|
|
@@ -17,7 +17,7 @@ export const registerTool = (server) => {
|
|
|
17
17
|
return createVariantMcpToolSuccessResponse(response.rawData);
|
|
18
18
|
}
|
|
19
19
|
catch (error) {
|
|
20
|
-
return handleMcpToolError(error, "Language Variant Retrieval");
|
|
20
|
+
return handleMcpToolError(error, "Latest Language Variant Retrieval");
|
|
21
21
|
}
|
|
22
22
|
});
|
|
23
23
|
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { createMapiClient } from "../clients/kontentClients.js";
|
|
3
|
+
import { handleMcpToolError } from "../utils/errorHandler.js";
|
|
4
|
+
import { createVariantMcpToolSuccessResponse } from "../utils/responseHelper.js";
|
|
5
|
+
export const registerTool = (server) => {
|
|
6
|
+
server.tool("get-published-variant-mapi", "Get published version of Kontent.ai language variant from Management API", {
|
|
7
|
+
itemId: z.string().describe("Item ID"),
|
|
8
|
+
languageId: z.string().describe("Language variant ID"),
|
|
9
|
+
}, async ({ itemId, languageId }, { authInfo: { token, clientId } = {} }) => {
|
|
10
|
+
const client = createMapiClient(clientId, token);
|
|
11
|
+
try {
|
|
12
|
+
const response = await client
|
|
13
|
+
.viewLanguageVariant()
|
|
14
|
+
.byItemId(itemId)
|
|
15
|
+
.byLanguageId(languageId)
|
|
16
|
+
.published()
|
|
17
|
+
.toPromise();
|
|
18
|
+
return createVariantMcpToolSuccessResponse(response.rawData);
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
return handleMcpToolError(error, "Published Language Variant Retrieval");
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { createMapiClient } from "../clients/kontentClients.js";
|
|
2
|
+
import { handleMcpToolError } from "../utils/errorHandler.js";
|
|
3
|
+
import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
|
|
4
|
+
export const registerTool = (server) => {
|
|
5
|
+
server.tool("list-collections-mapi", "Get all Kontent.ai collections", {}, async (_, { authInfo: { token, clientId } = {} }) => {
|
|
6
|
+
const client = createMapiClient(clientId, token);
|
|
7
|
+
try {
|
|
8
|
+
const response = await client.listCollections().toPromise();
|
|
9
|
+
return createMcpToolSuccessResponse(response.rawData);
|
|
10
|
+
}
|
|
11
|
+
catch (error) {
|
|
12
|
+
return handleMcpToolError(error, "Collections Listing");
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { createMapiClient } from "../clients/kontentClients.js";
|
|
2
|
+
import { handleMcpToolError } from "../utils/errorHandler.js";
|
|
3
|
+
import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
|
|
4
|
+
export const registerTool = (server) => {
|
|
5
|
+
server.tool("list-spaces-mapi", "Get all Kontent.ai spaces from Management API", {}, async (_, { authInfo: { token, clientId } = {} }) => {
|
|
6
|
+
const client = createMapiClient(clientId, token);
|
|
7
|
+
try {
|
|
8
|
+
const response = await client.listSpaces().toPromise();
|
|
9
|
+
return createMcpToolSuccessResponse(response.rawData);
|
|
10
|
+
}
|
|
11
|
+
catch (error) {
|
|
12
|
+
return handleMcpToolError(error, "Spaces Listing");
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { createMapiClient } from "../clients/kontentClients.js";
|
|
2
|
+
import { listVariantsCollectionSchema } from "../schemas/listSchemas.js";
|
|
3
|
+
import { handleMcpToolError } from "../utils/errorHandler.js";
|
|
4
|
+
import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
|
|
5
|
+
export const registerTool = (server) => {
|
|
6
|
+
server.tool("list-variants-collection-mapi", "List Kontent.ai language variants by collection from Management API (paginated)", listVariantsCollectionSchema.describe("Use list-collections-mapi to get collection ID if not provided").shape, async ({ collectionId, continuation_token }, { authInfo: { token, clientId } = {} }) => {
|
|
7
|
+
const client = createMapiClient(clientId, token);
|
|
8
|
+
try {
|
|
9
|
+
const query = client
|
|
10
|
+
.listLanguageVariantsByCollection()
|
|
11
|
+
.byCollectionId(collectionId);
|
|
12
|
+
const response = await (continuation_token
|
|
13
|
+
? query.xContinuationToken(continuation_token)
|
|
14
|
+
: query).toPromise();
|
|
15
|
+
return createMcpToolSuccessResponse({
|
|
16
|
+
variants: response.rawData.variants,
|
|
17
|
+
pagination: {
|
|
18
|
+
continuation_token: response.data.pagination.continuationToken,
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
return handleMcpToolError(error, "Collection Variants Listing");
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { createMapiClient } from "../clients/kontentClients.js";
|
|
2
|
+
import { listVariantsComponentsTypeSchema } from "../schemas/listSchemas.js";
|
|
3
|
+
import { handleMcpToolError } from "../utils/errorHandler.js";
|
|
4
|
+
import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
|
|
5
|
+
export const registerTool = (server) => {
|
|
6
|
+
server.tool("list-variants-components-type-mapi", "List Kontent.ai language variants containing components of a specific content type from Management API (paginated)", listVariantsComponentsTypeSchema.shape, async ({ contentTypeId, continuation_token }, { authInfo: { token, clientId } = {} }) => {
|
|
7
|
+
const client = createMapiClient(clientId, token);
|
|
8
|
+
try {
|
|
9
|
+
const query = client
|
|
10
|
+
.listLanguageVariantsOfContentTypeWithComponents()
|
|
11
|
+
.byTypeId(contentTypeId);
|
|
12
|
+
const response = await (continuation_token
|
|
13
|
+
? query.xContinuationToken(continuation_token)
|
|
14
|
+
: query).toPromise();
|
|
15
|
+
return createMcpToolSuccessResponse({
|
|
16
|
+
variants: response.rawData.variants,
|
|
17
|
+
pagination: {
|
|
18
|
+
continuation_token: response.data.pagination.continuationToken,
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
return handleMcpToolError(error, "Content Type Variants With Components Listing");
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { createMapiClient } from "../clients/kontentClients.js";
|
|
3
|
+
import { handleMcpToolError } from "../utils/errorHandler.js";
|
|
4
|
+
import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
|
|
5
|
+
export const registerTool = (server) => {
|
|
6
|
+
server.tool("list-variants-item-mapi", "List all Kontent.ai language variants of a content item from Management API", {
|
|
7
|
+
itemId: z.string().describe("Content item ID"),
|
|
8
|
+
}, async ({ itemId }, { authInfo: { token, clientId } = {} }) => {
|
|
9
|
+
const client = createMapiClient(clientId, token);
|
|
10
|
+
try {
|
|
11
|
+
const response = await client
|
|
12
|
+
.listLanguageVariantsOfItem()
|
|
13
|
+
.byItemId(itemId)
|
|
14
|
+
.toPromise();
|
|
15
|
+
return createMcpToolSuccessResponse(response.rawData);
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
return handleMcpToolError(error, "Item Variants Listing");
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { createMapiClient } from "../clients/kontentClients.js";
|
|
2
|
+
import { listVariantsSpaceSchema } from "../schemas/listSchemas.js";
|
|
3
|
+
import { handleMcpToolError } from "../utils/errorHandler.js";
|
|
4
|
+
import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
|
|
5
|
+
export const registerTool = (server) => {
|
|
6
|
+
server.tool("list-variants-space-mapi", "List Kontent.ai language variants by space from Management API (paginated)", listVariantsSpaceSchema.shape, async ({ spaceId, continuation_token }, { authInfo: { token, clientId } = {} }) => {
|
|
7
|
+
const client = createMapiClient(clientId, token);
|
|
8
|
+
try {
|
|
9
|
+
const query = client.listLanguageVariantsBySpace().bySpaceId(spaceId);
|
|
10
|
+
const response = await (continuation_token
|
|
11
|
+
? query.xContinuationToken(continuation_token)
|
|
12
|
+
: query).toPromise();
|
|
13
|
+
return createMcpToolSuccessResponse({
|
|
14
|
+
variants: response.rawData.variants,
|
|
15
|
+
pagination: {
|
|
16
|
+
continuation_token: response.data.pagination.continuationToken,
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
return handleMcpToolError(error, "Space Variants Listing");
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { createMapiClient } from "../clients/kontentClients.js";
|
|
2
|
+
import { listVariantsTypeSchema } from "../schemas/listSchemas.js";
|
|
3
|
+
import { handleMcpToolError } from "../utils/errorHandler.js";
|
|
4
|
+
import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
|
|
5
|
+
export const registerTool = (server) => {
|
|
6
|
+
server.tool("list-variants-type-mapi", "List Kontent.ai language variants by content type from Management API (paginated)", listVariantsTypeSchema.shape, async ({ contentTypeId, continuation_token }, { authInfo: { token, clientId } = {} }) => {
|
|
7
|
+
const client = createMapiClient(clientId, token);
|
|
8
|
+
try {
|
|
9
|
+
const query = client
|
|
10
|
+
.listLanguageVariantsOfContentType()
|
|
11
|
+
.byTypeId(contentTypeId);
|
|
12
|
+
const response = await (continuation_token
|
|
13
|
+
? query.xContinuationToken(continuation_token)
|
|
14
|
+
: query).toPromise();
|
|
15
|
+
return createMcpToolSuccessResponse({
|
|
16
|
+
variants: response.rawData.variants,
|
|
17
|
+
pagination: {
|
|
18
|
+
continuation_token: response.data.pagination.continuationToken,
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
return handleMcpToolError(error, "Content Type Variants Listing");
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { createMapiClient } from "../clients/kontentClients.js";
|
|
2
|
+
import { collectionPatchOperationsSchema } from "../schemas/collectionSchemas.js";
|
|
3
|
+
import { handleMcpToolError } from "../utils/errorHandler.js";
|
|
4
|
+
import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
|
|
5
|
+
export const registerTool = (server) => {
|
|
6
|
+
server.tool("patch-collections-mapi", "Update Kontent.ai collections using patch operations (addInto, move, remove, replace)", {
|
|
7
|
+
operations: collectionPatchOperationsSchema.describe("Patch operations array. Call list-collections-mapi first. Use addInto to add new collections, move to reorder, remove to delete empty collections, replace to rename."),
|
|
8
|
+
}, async ({ operations }, { authInfo: { token, clientId } = {} }) => {
|
|
9
|
+
const client = createMapiClient(clientId, token);
|
|
10
|
+
try {
|
|
11
|
+
const response = await client
|
|
12
|
+
.setCollections()
|
|
13
|
+
.withData(operations)
|
|
14
|
+
.toPromise();
|
|
15
|
+
return createMcpToolSuccessResponse({
|
|
16
|
+
message: `Collections updated successfully with ${operations.length} operation(s)`,
|
|
17
|
+
collections: response.rawData,
|
|
18
|
+
appliedOperations: operations,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
return handleMcpToolError(error, "Collections Patch");
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { createMapiClient } from "../clients/kontentClients.js";
|
|
2
|
+
import { patchLanguageSchema } 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("patch-language-mapi", "Update Kontent.ai language using replace operations via Management API. Only active languages can be modified - if deactivated, is_active: true must be first operation.", patchLanguageSchema.shape, async ({ languageId, operations }, { authInfo: { token, clientId } = {} }) => {
|
|
7
|
+
const client = createMapiClient(clientId, token);
|
|
8
|
+
try {
|
|
9
|
+
const response = await client
|
|
10
|
+
.modifyLanguage()
|
|
11
|
+
.byLanguageId(languageId)
|
|
12
|
+
.withData(operations)
|
|
13
|
+
.toPromise();
|
|
14
|
+
return createMcpToolSuccessResponse({
|
|
15
|
+
message: `Language updated with ${operations.length} operation(s)`,
|
|
16
|
+
language: response.rawData,
|
|
17
|
+
appliedOperations: operations,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
return handleMcpToolError(error, "Language Patch");
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
};
|
|
@@ -55,7 +55,7 @@ export function removeEmptyElementsFromVariant(obj) {
|
|
|
55
55
|
}
|
|
56
56
|
return result;
|
|
57
57
|
}
|
|
58
|
-
|
|
58
|
+
function removeEmptyValuesRecursive(obj) {
|
|
59
59
|
if (obj === null || obj === undefined) {
|
|
60
60
|
return undefined;
|
|
61
61
|
}
|
|
@@ -64,13 +64,13 @@ export function removeEmptyValues(obj) {
|
|
|
64
64
|
}
|
|
65
65
|
if (Array.isArray(obj)) {
|
|
66
66
|
const cleaned = obj
|
|
67
|
-
.map((item) =>
|
|
67
|
+
.map((item) => removeEmptyValuesRecursive(item))
|
|
68
68
|
.filter((item) => item !== undefined);
|
|
69
69
|
return cleaned.length === 0 ? undefined : cleaned;
|
|
70
70
|
}
|
|
71
71
|
const cleaned = {};
|
|
72
72
|
for (const [key, value] of Object.entries(obj)) {
|
|
73
|
-
const cleanedValue =
|
|
73
|
+
const cleanedValue = removeEmptyValuesRecursive(value);
|
|
74
74
|
if (cleanedValue !== undefined) {
|
|
75
75
|
cleaned[key] = cleanedValue;
|
|
76
76
|
}
|
|
@@ -78,25 +78,51 @@ export function removeEmptyValues(obj) {
|
|
|
78
78
|
const keys = Object.keys(cleaned);
|
|
79
79
|
return keys.length === 0 ? undefined : cleaned;
|
|
80
80
|
}
|
|
81
|
-
export
|
|
81
|
+
export function removeEmptyValues(obj) {
|
|
82
|
+
// At root level, preserve the structure even if empty
|
|
83
|
+
if (typeof obj !== "object" || obj === null) {
|
|
84
|
+
return obj;
|
|
85
|
+
}
|
|
86
|
+
if (Array.isArray(obj)) {
|
|
87
|
+
return obj
|
|
88
|
+
.map((item) => removeEmptyValuesRecursive(item))
|
|
89
|
+
.filter((item) => item !== undefined);
|
|
90
|
+
}
|
|
91
|
+
const cleaned = {};
|
|
92
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
93
|
+
const cleanedValue = removeEmptyValuesRecursive(value);
|
|
94
|
+
if (cleanedValue !== undefined) {
|
|
95
|
+
cleaned[key] = cleanedValue;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return cleaned;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Converts data to MCP tool success response format.
|
|
102
|
+
* Handles undefined separately as JSON.stringify(undefined) returns undefined (not a string).
|
|
103
|
+
* Skips stringify for strings as they don't need JSON encoding for MCP text response.
|
|
104
|
+
*/
|
|
105
|
+
const toMcpSuccessResponse = (data) => {
|
|
106
|
+
const text = data === undefined
|
|
107
|
+
? "undefined"
|
|
108
|
+
: typeof data === "string"
|
|
109
|
+
? data
|
|
110
|
+
: JSON.stringify(data);
|
|
82
111
|
return {
|
|
83
112
|
content: [
|
|
84
113
|
{
|
|
85
114
|
type: "text",
|
|
86
|
-
text
|
|
115
|
+
text,
|
|
87
116
|
},
|
|
88
117
|
],
|
|
89
118
|
};
|
|
90
119
|
};
|
|
120
|
+
export const createMcpToolSuccessResponse = (data) => {
|
|
121
|
+
const cleaned = removeEmptyValues(data);
|
|
122
|
+
return toMcpSuccessResponse(cleaned);
|
|
123
|
+
};
|
|
91
124
|
export const createVariantMcpToolSuccessResponse = (data) => {
|
|
92
125
|
const cleaned = removeEmptyValues(data);
|
|
93
126
|
const optimized = removeEmptyElementsFromVariant(cleaned);
|
|
94
|
-
return
|
|
95
|
-
content: [
|
|
96
|
-
{
|
|
97
|
-
type: "text",
|
|
98
|
-
text: JSON.stringify(optimized),
|
|
99
|
-
},
|
|
100
|
-
],
|
|
101
|
-
};
|
|
127
|
+
return toMcpSuccessResponse(optimized);
|
|
102
128
|
};
|
package/package.json
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kontent-ai/mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"mcpName": "io.github.kontent-ai/mcp-server",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/kontent-ai/mcp-server"
|
|
9
|
+
},
|
|
6
10
|
"scripts": {
|
|
7
11
|
"build": "rimraf build && tsc --project scripts/tsconfig.json && tsc",
|
|
8
12
|
"start:stdio": "node build/bin.js stdio",
|
|
@@ -25,7 +29,7 @@
|
|
|
25
29
|
"author": "Jiri Lojda",
|
|
26
30
|
"license": "MIT",
|
|
27
31
|
"dependencies": {
|
|
28
|
-
"@kontent-ai/management-sdk": "^
|
|
32
|
+
"@kontent-ai/management-sdk": "^8.1.0",
|
|
29
33
|
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
30
34
|
"applicationinsights": "^2.9.8",
|
|
31
35
|
"dotenv": "^16.5.0",
|