@kontent-ai/mcp-server 0.17.0 → 0.19.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 +3 -2
- package/build/bin.js +21 -7
- package/build/clients/kontentClients.js +12 -7
- package/build/config/appConfiguration.js +51 -0
- package/build/schemas/searchOperationSchemas.js +14 -0
- package/build/server.js +30 -30
- package/build/telemetry/applicationInsights.js +157 -0
- package/build/telemetry/telemetrySanitizer.js +48 -0
- 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 +3 -3
- package/build/tools/change-variant-workflow-step-mapi.js +3 -3
- package/build/tools/context/initial-context.js +11 -1
- package/build/tools/create-variant-version-mapi.js +3 -3
- package/build/tools/delete-content-item-mapi.js +3 -3
- package/build/tools/delete-content-type-mapi.js +3 -3
- package/build/tools/delete-language-variant-mapi.js +3 -3
- package/build/tools/filter-variants-mapi.js +23 -42
- package/build/tools/get-asset-mapi.js +3 -3
- package/build/tools/get-item-mapi.js +3 -3
- package/build/tools/get-taxonomy-group-mapi.js +3 -3
- package/build/tools/get-type-mapi.js +3 -3
- package/build/tools/get-type-snippet-mapi.js +3 -3
- package/build/tools/get-variant-mapi.js +3 -3
- package/build/tools/list-assets-mapi.js +4 -3
- package/build/tools/list-content-type-snippets-mapi.js +4 -3
- package/build/tools/list-content-types-mapi.js +4 -3
- package/build/tools/list-languages-mapi.js +4 -3
- package/build/tools/list-taxonomy-groups-mapi.js +4 -3
- package/build/tools/list-workflows-mapi.js +3 -3
- package/build/tools/patch-content-type-mapi.js +2 -2
- package/build/tools/publish-variant-mapi.js +2 -2
- package/build/tools/search-variants-mapi.js +95 -0
- 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 -1
- package/package.json +6 -2
- package/build/tools/get-item-dapi.js +0 -23
package/README.md
CHANGED
|
@@ -91,7 +91,8 @@ npx @kontent-ai/mcp-server@latest shttp
|
|
|
91
91
|
* **upsert-language-variant-mapi** – Create or update Kontent.ai language variant of a content item via Management API. This adds actual content to the content item elements. When updating an existing variant, only the provided elements will be modified
|
|
92
92
|
* **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
|
|
93
93
|
* **delete-language-variant-mapi** – Delete Kontent.ai language variant from Management API
|
|
94
|
-
* **filter-variants-mapi** –
|
|
94
|
+
* **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.)
|
|
95
|
+
* **search-variants-mapi** – AI-powered semantic search for finding content by meaning and concepts in a specific language variant. Use for: conceptual searches when you don't know exact keywords. Limited filtering options (variant ID only)
|
|
95
96
|
|
|
96
97
|
### Asset Management
|
|
97
98
|
|
|
@@ -345,4 +346,4 @@ MIT
|
|
|
345
346
|
[discord-shield]: https://img.shields.io/discord/821885171984891914?color=%237289DA&label=Kontent.ai%20Discord&logo=discord&style=for-the-badge
|
|
346
347
|
[discord-url]: https://discord.com/invite/SKCxwPtevJ
|
|
347
348
|
[npm-url]: https://www.npmjs.com/package/@kontent-ai/mcp-server
|
|
348
|
-
[npm-shield]: https://img.shields.io/npm/v/%40kontent-ai%2Fmcp-server?style=for-the-badge&logo=npm&color=%23CB0000
|
|
349
|
+
[npm-shield]: https://img.shields.io/npm/v/%40kontent-ai%2Fmcp-server?style=for-the-badge&logo=npm&color=%23CB0000
|
package/build/bin.js
CHANGED
|
@@ -4,16 +4,18 @@ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/
|
|
|
4
4
|
import "dotenv/config";
|
|
5
5
|
import express from "express";
|
|
6
6
|
import packageJson from "../package.json" with { type: "json" };
|
|
7
|
+
import { loadAppConfiguration, } from "./config/appConfiguration.js";
|
|
7
8
|
import { createServer } from "./server.js";
|
|
9
|
+
import { initializeApplicationInsights, trackException, trackServerStartup, } from "./telemetry/applicationInsights.js";
|
|
8
10
|
import { extractBearerToken } from "./utils/extractBearerToken.js";
|
|
9
11
|
import { isValidGuid } from "./utils/isValidGuid.js";
|
|
10
12
|
const version = packageJson.version;
|
|
11
|
-
async function startStreamableHTTP() {
|
|
13
|
+
async function startStreamableHTTP(config) {
|
|
12
14
|
const app = express();
|
|
13
15
|
app.use(express.json());
|
|
14
16
|
app.post("/mcp", async (req, res) => {
|
|
15
17
|
try {
|
|
16
|
-
const { server } = createServer();
|
|
18
|
+
const { server } = createServer(config);
|
|
17
19
|
const transport = new StreamableHTTPServerTransport({
|
|
18
20
|
sessionIdGenerator: undefined,
|
|
19
21
|
});
|
|
@@ -27,6 +29,7 @@ async function startStreamableHTTP() {
|
|
|
27
29
|
}
|
|
28
30
|
catch (error) {
|
|
29
31
|
console.error("Error handling MCP request:", error);
|
|
32
|
+
trackException(error, "MCP Request Handler");
|
|
30
33
|
if (!res.headersSent) {
|
|
31
34
|
res.status(500).json({
|
|
32
35
|
jsonrpc: "2.0",
|
|
@@ -68,7 +71,7 @@ async function startStreamableHTTP() {
|
|
|
68
71
|
});
|
|
69
72
|
return;
|
|
70
73
|
}
|
|
71
|
-
const { server } = createServer();
|
|
74
|
+
const { server } = createServer(config);
|
|
72
75
|
const transport = new StreamableHTTPServerTransport({
|
|
73
76
|
sessionIdGenerator: undefined,
|
|
74
77
|
});
|
|
@@ -95,6 +98,7 @@ async function startStreamableHTTP() {
|
|
|
95
98
|
}
|
|
96
99
|
catch (error) {
|
|
97
100
|
console.error("Error handling MCP request:", error);
|
|
101
|
+
trackException(error, "MCP Multi-tenant Request Handler");
|
|
98
102
|
if (!res.headersSent) {
|
|
99
103
|
res.status(500).json({
|
|
100
104
|
jsonrpc: "2.0",
|
|
@@ -127,6 +131,10 @@ async function startStreamableHTTP() {
|
|
|
127
131
|
id: null,
|
|
128
132
|
}));
|
|
129
133
|
});
|
|
134
|
+
app.use((err, _req, _res, next) => {
|
|
135
|
+
trackException(err, "Express Error Handler");
|
|
136
|
+
next(err);
|
|
137
|
+
});
|
|
130
138
|
const PORT = process.env.PORT || 3001;
|
|
131
139
|
app.listen(PORT, () => {
|
|
132
140
|
console.log(`Kontent.ai MCP Server v${version} (Streamable HTTP) running on port ${PORT}.
|
|
@@ -135,13 +143,18 @@ Available endpoints:
|
|
|
135
143
|
/{environmentId}/mcp (requires Bearer authentication)`);
|
|
136
144
|
});
|
|
137
145
|
}
|
|
138
|
-
async function startStdio() {
|
|
139
|
-
const { server } = createServer();
|
|
146
|
+
async function startStdio(config) {
|
|
147
|
+
const { server } = createServer(config);
|
|
140
148
|
const transport = new StdioServerTransport();
|
|
141
149
|
console.log(`Kontent.ai MCP Server v${version} (stdio) starting`);
|
|
142
150
|
await server.connect(transport);
|
|
143
151
|
}
|
|
144
152
|
async function main() {
|
|
153
|
+
const config = await loadAppConfiguration();
|
|
154
|
+
if (config) {
|
|
155
|
+
initializeApplicationInsights(config);
|
|
156
|
+
trackServerStartup(version);
|
|
157
|
+
}
|
|
145
158
|
const args = process.argv.slice(2);
|
|
146
159
|
const transportType = args[0]?.toLowerCase();
|
|
147
160
|
if (!transportType ||
|
|
@@ -150,13 +163,14 @@ async function main() {
|
|
|
150
163
|
process.exit(1);
|
|
151
164
|
}
|
|
152
165
|
if (transportType === "stdio") {
|
|
153
|
-
await startStdio();
|
|
166
|
+
await startStdio(config);
|
|
154
167
|
}
|
|
155
168
|
else if (transportType === "shttp") {
|
|
156
|
-
await startStreamableHTTP();
|
|
169
|
+
await startStreamableHTTP(config);
|
|
157
170
|
}
|
|
158
171
|
}
|
|
159
172
|
main().catch((error) => {
|
|
160
173
|
console.error("Fatal error:", error);
|
|
174
|
+
trackException(error, "Server Startup");
|
|
161
175
|
process.exit(1);
|
|
162
176
|
});
|
|
@@ -6,9 +6,18 @@ const sourceTrackingHeaderName = "X-KC-SOURCE";
|
|
|
6
6
|
* Creates a Kontent.ai Management API client
|
|
7
7
|
* @param environmentId Optional environment ID (defaults to process.env.KONTENT_ENVIRONMENT_ID)
|
|
8
8
|
* @param apiKey Optional API key (defaults to process.env.KONTENT_API_KEY)
|
|
9
|
+
* @param config Optional configuration object
|
|
10
|
+
* @param additionalHeaders Optional additional headers to include in requests
|
|
9
11
|
* @returns Management API client instance
|
|
10
12
|
*/
|
|
11
|
-
export const createMapiClient = (environmentId, apiKey) => {
|
|
13
|
+
export const createMapiClient = (environmentId, apiKey, config, additionalHeaders) => {
|
|
14
|
+
const allHeaders = [
|
|
15
|
+
{
|
|
16
|
+
header: sourceTrackingHeaderName,
|
|
17
|
+
value: `${packageJson.name};${packageJson.version}`,
|
|
18
|
+
},
|
|
19
|
+
...(additionalHeaders || []),
|
|
20
|
+
];
|
|
12
21
|
return createManagementClient({
|
|
13
22
|
apiKey: apiKey ??
|
|
14
23
|
process.env.KONTENT_API_KEY ??
|
|
@@ -16,11 +25,7 @@ export const createMapiClient = (environmentId, apiKey) => {
|
|
|
16
25
|
environmentId: environmentId ??
|
|
17
26
|
process.env.KONTENT_ENVIRONMENT_ID ??
|
|
18
27
|
throwError("KONTENT_ENVIRONMENT_ID is not set"),
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
header: sourceTrackingHeaderName,
|
|
22
|
-
value: `${packageJson.name};${packageJson.version}`,
|
|
23
|
-
},
|
|
24
|
-
],
|
|
28
|
+
baseUrl: config ? `${config.manageApiUrl}v2` : undefined,
|
|
29
|
+
headers: allHeaders,
|
|
25
30
|
});
|
|
26
31
|
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { load } from "@azure/app-configuration-provider";
|
|
2
|
+
import { DefaultAzureCredential } from "@azure/identity";
|
|
3
|
+
function loadIndexedEnvVars(prefix) {
|
|
4
|
+
const values = [];
|
|
5
|
+
let index = 0;
|
|
6
|
+
while (true) {
|
|
7
|
+
const envVar = process.env[`${prefix}__${index}`];
|
|
8
|
+
if (!envVar)
|
|
9
|
+
break;
|
|
10
|
+
const trimmed = envVar.trim();
|
|
11
|
+
if (trimmed) {
|
|
12
|
+
values.push(trimmed);
|
|
13
|
+
}
|
|
14
|
+
index++;
|
|
15
|
+
}
|
|
16
|
+
return values;
|
|
17
|
+
}
|
|
18
|
+
function getConfigValue(configMap, key) {
|
|
19
|
+
const value = configMap.get(key);
|
|
20
|
+
return value && typeof value === "string" ? value : undefined;
|
|
21
|
+
}
|
|
22
|
+
export async function loadAppConfiguration() {
|
|
23
|
+
try {
|
|
24
|
+
const appConfigEndpoint = process.env.ConfigStore__Endpoints__0;
|
|
25
|
+
const labels = loadIndexedEnvVars("ConfigStore__Labels");
|
|
26
|
+
if (!appConfigEndpoint || labels.length === 0) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
const selectors = labels.flatMap((label) => [
|
|
30
|
+
{ keyFilter: "ApplicationInsights:*", labelFilter: label },
|
|
31
|
+
{ keyFilter: "Global:Runtime:*", labelFilter: label },
|
|
32
|
+
{ keyFilter: "Draft:ManageApi:*", labelFilter: label },
|
|
33
|
+
]);
|
|
34
|
+
const credential = new DefaultAzureCredential();
|
|
35
|
+
const configMap = await load(appConfigEndpoint, credential, {
|
|
36
|
+
selectors: selectors,
|
|
37
|
+
keyVaultOptions: {
|
|
38
|
+
credential: credential,
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
return {
|
|
42
|
+
applicationInsightsConnectionString: getConfigValue(configMap, "ApplicationInsights:ConnectionString"),
|
|
43
|
+
projectLocation: getConfigValue(configMap, "Global:Runtime:ProjectLocation"),
|
|
44
|
+
manageApiUrl: getConfigValue(configMap, "Draft:ManageApi:Url"),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
console.log("Failed to load App Configuration:", error);
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const searchOperationSchema = z.object({
|
|
3
|
+
searchPhrase: z
|
|
4
|
+
.string()
|
|
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
|
+
filter: z
|
|
7
|
+
.object({
|
|
8
|
+
variantId: z
|
|
9
|
+
.string()
|
|
10
|
+
.uuid()
|
|
11
|
+
.describe("UUID of the language variant to filter by"),
|
|
12
|
+
})
|
|
13
|
+
.describe("Mandatory content item variant filter to restrict search scope. Must specify a valid variant UUID"),
|
|
14
|
+
});
|
package/build/server.js
CHANGED
|
@@ -12,7 +12,6 @@ import { registerTool as registerDeleteLanguageVariantMapi } from "./tools/delet
|
|
|
12
12
|
import { registerTool as registerFilterVariantsMapi } from "./tools/filter-variants-mapi.js";
|
|
13
13
|
import { registerTool as registerGetAssetMapi } from "./tools/get-asset-mapi.js";
|
|
14
14
|
import { registerTool as registerGetInitialContext } from "./tools/get-initial-context.js";
|
|
15
|
-
import { registerTool as registerGetItemDapi } from "./tools/get-item-dapi.js";
|
|
16
15
|
import { registerTool as registerGetItemMapi } from "./tools/get-item-mapi.js";
|
|
17
16
|
import { registerTool as registerGetTaxonomyGroupMapi } from "./tools/get-taxonomy-group-mapi.js";
|
|
18
17
|
import { registerTool as registerGetTypeMapi } from "./tools/get-type-mapi.js";
|
|
@@ -26,11 +25,12 @@ import { registerTool as registerListTaxonomyGroupsMapi } from "./tools/list-tax
|
|
|
26
25
|
import { registerTool as registerListWorkflowsMapi } from "./tools/list-workflows-mapi.js";
|
|
27
26
|
import { registerTool as registerPatchContentTypeMapi } from "./tools/patch-content-type-mapi.js";
|
|
28
27
|
import { registerTool as registerPublishVariantMapi } from "./tools/publish-variant-mapi.js";
|
|
28
|
+
import { registerTool as registerSearchVariantsMapi } from "./tools/search-variants-mapi.js";
|
|
29
29
|
import { registerTool as registerUnpublishVariantMapi } from "./tools/unpublish-variant-mapi.js";
|
|
30
30
|
import { registerTool as registerUpdateContentItemMapi } from "./tools/update-content-item-mapi.js";
|
|
31
31
|
import { registerTool as registerUpsertLanguageVariantMapi } from "./tools/upsert-language-variant-mapi.js";
|
|
32
32
|
// Create server instance
|
|
33
|
-
export const createServer = () => {
|
|
33
|
+
export const createServer = (config) => {
|
|
34
34
|
const server = new McpServer({
|
|
35
35
|
name: "kontent-ai",
|
|
36
36
|
version: packageJson.version,
|
|
@@ -41,33 +41,33 @@ export const createServer = () => {
|
|
|
41
41
|
});
|
|
42
42
|
// Register all tools
|
|
43
43
|
registerGetInitialContext(server);
|
|
44
|
-
registerGetItemMapi(server);
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
registerPublishVariantMapi(server);
|
|
71
|
-
registerUnpublishVariantMapi(server);
|
|
44
|
+
registerGetItemMapi(server, config);
|
|
45
|
+
registerGetVariantMapi(server, config);
|
|
46
|
+
registerGetTypeMapi(server, config);
|
|
47
|
+
registerListContentTypesMapi(server, config);
|
|
48
|
+
registerDeleteContentTypeMapi(server, config);
|
|
49
|
+
registerListLanguagesMapi(server, config);
|
|
50
|
+
registerGetAssetMapi(server, config);
|
|
51
|
+
registerListAssetsMapi(server, config);
|
|
52
|
+
registerAddContentTypeMapi(server, config);
|
|
53
|
+
registerPatchContentTypeMapi(server, config);
|
|
54
|
+
registerAddContentTypeSnippetMapi(server, config);
|
|
55
|
+
registerGetTypeSnippetMapi(server, config);
|
|
56
|
+
registerListContentTypeSnippetsMapi(server, config);
|
|
57
|
+
registerAddTaxonomyGroupMapi(server, config);
|
|
58
|
+
registerListTaxonomyGroupsMapi(server, config);
|
|
59
|
+
registerGetTaxonomyGroupMapi(server, config);
|
|
60
|
+
registerAddContentItemMapi(server, config);
|
|
61
|
+
registerUpdateContentItemMapi(server, config);
|
|
62
|
+
registerDeleteContentItemMapi(server, config);
|
|
63
|
+
registerUpsertLanguageVariantMapi(server, config);
|
|
64
|
+
registerCreateVariantVersionMapi(server, config);
|
|
65
|
+
registerDeleteLanguageVariantMapi(server, config);
|
|
66
|
+
registerListWorkflowsMapi(server, config);
|
|
67
|
+
registerChangeVariantWorkflowStepMapi(server, config);
|
|
68
|
+
registerFilterVariantsMapi(server, config);
|
|
69
|
+
registerSearchVariantsMapi(server, config);
|
|
70
|
+
registerPublishVariantMapi(server, config);
|
|
71
|
+
registerUnpublishVariantMapi(server, config);
|
|
72
72
|
return { server };
|
|
73
73
|
};
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { SharedModels } from "@kontent-ai/management-sdk";
|
|
2
|
+
import appInsights from "applicationinsights";
|
|
3
|
+
import { AxiosError } from "axios";
|
|
4
|
+
import { sanitizeTelemetry, sanitizeUrl } from "./telemetrySanitizer.js";
|
|
5
|
+
let isInitialized = false;
|
|
6
|
+
function trackKontentApiError(error, context) {
|
|
7
|
+
const safeError = new Error(error.message);
|
|
8
|
+
safeError.stack = error.originalError.stack || safeError.stack;
|
|
9
|
+
appInsights.defaultClient.trackException({
|
|
10
|
+
exception: safeError,
|
|
11
|
+
properties: {
|
|
12
|
+
errorType: "KontentManagementApiError",
|
|
13
|
+
errorCode: error.errorCode,
|
|
14
|
+
requestId: error.requestId,
|
|
15
|
+
context: context,
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
function trackHttpError(error, context) {
|
|
20
|
+
let errorMessage;
|
|
21
|
+
let properties = {
|
|
22
|
+
errorType: "HttpError",
|
|
23
|
+
axiosErrorCode: error.code,
|
|
24
|
+
method: error.config?.method?.toUpperCase(),
|
|
25
|
+
url: error.config?.url ? sanitizeUrl(error.config.url) : undefined,
|
|
26
|
+
context: context,
|
|
27
|
+
};
|
|
28
|
+
if (error.response) {
|
|
29
|
+
errorMessage = `HTTP ${error.response.status} ${error.response.statusText || "Error"}`;
|
|
30
|
+
properties = {
|
|
31
|
+
...properties,
|
|
32
|
+
scenario: "ServerResponseError",
|
|
33
|
+
status: error.response.status,
|
|
34
|
+
statusText: error.response.statusText,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
else if (error.request) {
|
|
38
|
+
errorMessage = "Network Error - No response received";
|
|
39
|
+
properties = {
|
|
40
|
+
...properties,
|
|
41
|
+
scenario: "NetworkError",
|
|
42
|
+
timeout: error.config?.timeout,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
errorMessage = `Request Setup Error: ${error.message}`;
|
|
47
|
+
properties = {
|
|
48
|
+
...properties,
|
|
49
|
+
scenario: "RequestSetupError",
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
const safeError = new Error(errorMessage);
|
|
53
|
+
safeError.stack = error.stack || safeError.stack;
|
|
54
|
+
appInsights.defaultClient.trackException({
|
|
55
|
+
exception: safeError,
|
|
56
|
+
properties,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
function trackGeneralError(error, context) {
|
|
60
|
+
const safeError = new Error(error.message);
|
|
61
|
+
safeError.stack = error.stack;
|
|
62
|
+
appInsights.defaultClient.trackException({
|
|
63
|
+
exception: safeError,
|
|
64
|
+
properties: {
|
|
65
|
+
errorType: "JavaScriptError",
|
|
66
|
+
name: error.name,
|
|
67
|
+
context: context,
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
function trackUnknownError(error, context) {
|
|
72
|
+
appInsights.defaultClient.trackException({
|
|
73
|
+
exception: new Error("An error occurred"),
|
|
74
|
+
properties: {
|
|
75
|
+
errorType: "UnknownError",
|
|
76
|
+
actualType: typeof error,
|
|
77
|
+
context: context,
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
export function trackException(error, context) {
|
|
82
|
+
try {
|
|
83
|
+
if (!isInitialized || !appInsights.defaultClient) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (error instanceof SharedModels.ContentManagementBaseKontentError) {
|
|
87
|
+
trackKontentApiError(error, context);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (error instanceof AxiosError) {
|
|
91
|
+
trackHttpError(error, context);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (error instanceof Error) {
|
|
95
|
+
trackGeneralError(error, context);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
trackUnknownError(error, context);
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
// Silently fail - tracking should never break the application
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
export function trackServerStartup(version) {
|
|
105
|
+
try {
|
|
106
|
+
if (isInitialized && appInsights.defaultClient) {
|
|
107
|
+
appInsights.defaultClient.trackEvent({
|
|
108
|
+
name: "ServerStartup",
|
|
109
|
+
properties: {
|
|
110
|
+
version: version,
|
|
111
|
+
timestamp: new Date().toISOString(),
|
|
112
|
+
nodeVersion: process.version,
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
// Silently fail - tracking should never break the application
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function createTelemetryProcessor(config) {
|
|
122
|
+
return (envelope) => {
|
|
123
|
+
sanitizeTelemetry(envelope);
|
|
124
|
+
if (envelope.data?.baseData) {
|
|
125
|
+
envelope.data.baseData.properties =
|
|
126
|
+
envelope.data.baseData.properties || {};
|
|
127
|
+
envelope.data.baseData.properties["component.name"] = "mcp-server";
|
|
128
|
+
envelope.data.baseData.properties["component.location"] =
|
|
129
|
+
config.projectLocation || "unknown";
|
|
130
|
+
}
|
|
131
|
+
return true;
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
export function initializeApplicationInsights(config) {
|
|
135
|
+
try {
|
|
136
|
+
if (!config?.applicationInsightsConnectionString) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
appInsights
|
|
140
|
+
.setup(config.applicationInsightsConnectionString)
|
|
141
|
+
.setAutoCollectExceptions(true)
|
|
142
|
+
.setAutoCollectRequests(true)
|
|
143
|
+
.setAutoCollectConsole(false)
|
|
144
|
+
.setAutoCollectDependencies(false)
|
|
145
|
+
.setAutoDependencyCorrelation(false)
|
|
146
|
+
.setAutoCollectHeartbeat(false)
|
|
147
|
+
.setAutoCollectPerformance(false)
|
|
148
|
+
.setAutoCollectIncomingRequestAzureFunctions(false)
|
|
149
|
+
.setAutoCollectPreAggregatedMetrics(false)
|
|
150
|
+
.start();
|
|
151
|
+
isInitialized = true;
|
|
152
|
+
appInsights.defaultClient.addTelemetryProcessor(createTelemetryProcessor(config));
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
console.log("Failed to initialize Application Insights:", error);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
const SENSITIVE_KEYWORDS = [
|
|
2
|
+
"authorization",
|
|
3
|
+
"x-authorization",
|
|
4
|
+
"x-api-key",
|
|
5
|
+
"bearer",
|
|
6
|
+
"token",
|
|
7
|
+
"password",
|
|
8
|
+
"secret",
|
|
9
|
+
"key",
|
|
10
|
+
"pwd",
|
|
11
|
+
"pass",
|
|
12
|
+
"credential",
|
|
13
|
+
"x-kc-",
|
|
14
|
+
"cookie",
|
|
15
|
+
];
|
|
16
|
+
function isSensitive(key) {
|
|
17
|
+
const lowerKey = key.toLowerCase();
|
|
18
|
+
return SENSITIVE_KEYWORDS.some((keyword) => lowerKey.includes(keyword));
|
|
19
|
+
}
|
|
20
|
+
function sanitizeObject(obj) {
|
|
21
|
+
if (!obj || typeof obj !== "object")
|
|
22
|
+
return;
|
|
23
|
+
Object.keys(obj).forEach((key) => {
|
|
24
|
+
if (isSensitive(key)) {
|
|
25
|
+
delete obj[key];
|
|
26
|
+
}
|
|
27
|
+
else if (typeof obj[key] === "object") {
|
|
28
|
+
sanitizeObject(obj[key]);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
export function sanitizeUrl(url) {
|
|
33
|
+
return url.split("?")[0];
|
|
34
|
+
}
|
|
35
|
+
export function sanitizeTelemetry(envelope) {
|
|
36
|
+
if (!envelope.data?.baseData)
|
|
37
|
+
return;
|
|
38
|
+
const data = envelope.data.baseData;
|
|
39
|
+
if (envelope.tags) {
|
|
40
|
+
sanitizeObject(envelope.tags);
|
|
41
|
+
}
|
|
42
|
+
if (data.properties) {
|
|
43
|
+
sanitizeObject(data.properties);
|
|
44
|
+
}
|
|
45
|
+
if (data.url) {
|
|
46
|
+
data.url = sanitizeUrl(data.url);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -2,7 +2,7 @@ import { z } from "zod";
|
|
|
2
2
|
import { createMapiClient } from "../clients/kontentClients.js";
|
|
3
3
|
import { handleMcpToolError } from "../utils/errorHandler.js";
|
|
4
4
|
import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
|
|
5
|
-
export const registerTool = (server) => {
|
|
5
|
+
export const registerTool = (server, config) => {
|
|
6
6
|
server.tool("add-content-item-mapi", "Add new Kontent.ai content item via Management API. This creates the content item structure but does not add content to language variants. Use upsert-language-variant-mapi to add content to the item.", {
|
|
7
7
|
name: z
|
|
8
8
|
.string()
|
|
@@ -33,7 +33,7 @@ export const registerTool = (server) => {
|
|
|
33
33
|
.optional()
|
|
34
34
|
.describe("Reference to a collection by id, codename, or external_id (optional)"),
|
|
35
35
|
}, async ({ name, type, codename, external_id, collection }, { authInfo: { token, clientId } = {} }) => {
|
|
36
|
-
const client = createMapiClient(clientId, token);
|
|
36
|
+
const client = createMapiClient(clientId, token, config);
|
|
37
37
|
try {
|
|
38
38
|
const response = await client
|
|
39
39
|
.addContentItem()
|
|
@@ -3,7 +3,7 @@ import { createMapiClient } from "../clients/kontentClients.js";
|
|
|
3
3
|
import { contentGroupSchema, elementSchema, } from "../schemas/contentTypeSchemas.js";
|
|
4
4
|
import { handleMcpToolError } from "../utils/errorHandler.js";
|
|
5
5
|
import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
|
|
6
|
-
export const registerTool = (server) => {
|
|
6
|
+
export const registerTool = (server, config) => {
|
|
7
7
|
server.tool("add-content-type-mapi", "Add new Kontent.ai content type via Management API", {
|
|
8
8
|
name: z.string().describe("Display name of the content type"),
|
|
9
9
|
codename: z
|
|
@@ -22,7 +22,7 @@ export const registerTool = (server) => {
|
|
|
22
22
|
.optional()
|
|
23
23
|
.describe("Array of content groups (optional)"),
|
|
24
24
|
}, async ({ name, codename, external_id, elements, content_groups }, { authInfo: { token, clientId } = {} }) => {
|
|
25
|
-
const client = createMapiClient(clientId, token);
|
|
25
|
+
const client = createMapiClient(clientId, token, config);
|
|
26
26
|
try {
|
|
27
27
|
const response = await client
|
|
28
28
|
.addContentType()
|
|
@@ -3,7 +3,7 @@ import { createMapiClient } from "../clients/kontentClients.js";
|
|
|
3
3
|
import { snippetElementSchema } from "../schemas/contentTypeSchemas.js";
|
|
4
4
|
import { handleMcpToolError } from "../utils/errorHandler.js";
|
|
5
5
|
import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
|
|
6
|
-
export const registerTool = (server) => {
|
|
6
|
+
export const registerTool = (server, config) => {
|
|
7
7
|
server.tool("add-content-type-snippet-mapi", "Add new Kontent.ai content type snippet via Management API", {
|
|
8
8
|
name: z.string().describe("Display name of the content type snippet"),
|
|
9
9
|
codename: z
|
|
@@ -18,7 +18,7 @@ export const registerTool = (server) => {
|
|
|
18
18
|
.array(snippetElementSchema)
|
|
19
19
|
.describe("Array of elements that define the structure of the content type snippet"),
|
|
20
20
|
}, async ({ name, codename, external_id, elements }, { authInfo: { token, clientId } = {} }) => {
|
|
21
|
-
const client = createMapiClient(clientId, token);
|
|
21
|
+
const client = createMapiClient(clientId, token, config);
|
|
22
22
|
try {
|
|
23
23
|
const response = await client
|
|
24
24
|
.addContentTypeSnippet()
|
|
@@ -2,15 +2,15 @@ import { createMapiClient } from "../clients/kontentClients.js";
|
|
|
2
2
|
import { taxonomyGroupSchemas } from "../schemas/taxonomySchemas.js";
|
|
3
3
|
import { handleMcpToolError } from "../utils/errorHandler.js";
|
|
4
4
|
import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
|
|
5
|
-
export const registerTool = (server) => {
|
|
5
|
+
export const registerTool = (server, config) => {
|
|
6
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);
|
|
7
|
+
const client = createMapiClient(clientId, token, config);
|
|
8
8
|
try {
|
|
9
9
|
const response = await client
|
|
10
10
|
.addTaxonomy()
|
|
11
11
|
.withData(taxonomyGroup)
|
|
12
12
|
.toPromise();
|
|
13
|
-
return createMcpToolSuccessResponse(response.
|
|
13
|
+
return createMcpToolSuccessResponse(response.rawData);
|
|
14
14
|
}
|
|
15
15
|
catch (error) {
|
|
16
16
|
return handleMcpToolError(error, "Taxonomy Group Creation");
|
|
@@ -2,7 +2,7 @@ import { z } from "zod";
|
|
|
2
2
|
import { createMapiClient } from "../clients/kontentClients.js";
|
|
3
3
|
import { handleMcpToolError } from "../utils/errorHandler.js";
|
|
4
4
|
import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
|
|
5
|
-
export const registerTool = (server) => {
|
|
5
|
+
export const registerTool = (server, config) => {
|
|
6
6
|
server.tool("change-variant-workflow-step-mapi", "Change the workflow step of a language variant in Kontent.ai. This operation moves a language variant to a different step in the workflow, enabling content lifecycle management such as moving content from draft to review, review to published, etc.", {
|
|
7
7
|
itemId: z
|
|
8
8
|
.string()
|
|
@@ -21,7 +21,7 @@ export const registerTool = (server) => {
|
|
|
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
23
|
}, async ({ itemId, languageId, workflowId, workflowStepId }, { authInfo: { token, clientId } = {} }) => {
|
|
24
|
-
const client = createMapiClient(clientId, token);
|
|
24
|
+
const client = createMapiClient(clientId, token, config);
|
|
25
25
|
try {
|
|
26
26
|
const response = await client
|
|
27
27
|
.changeWorkflowOfLanguageVariant()
|
|
@@ -38,7 +38,7 @@ export const registerTool = (server) => {
|
|
|
38
38
|
.toPromise();
|
|
39
39
|
return createMcpToolSuccessResponse({
|
|
40
40
|
message: `Successfully changed workflow step of language variant '${languageId}' for content item '${itemId}' to workflow step '${workflowStepId}'`,
|
|
41
|
-
result: response.
|
|
41
|
+
result: response.rawData,
|
|
42
42
|
});
|
|
43
43
|
}
|
|
44
44
|
catch (error) {
|
|
@@ -123,6 +123,8 @@ When working with taxonomy elements, always retrieve and understand the taxonomy
|
|
|
123
123
|
|
|
124
124
|
## MCP Tool Usage Guidelines
|
|
125
125
|
|
|
126
|
+
### ID Reference Preferences
|
|
127
|
+
|
|
126
128
|
**CRITICAL**: When using MCP tools, always prefer internal IDs over codenames:
|
|
127
129
|
|
|
128
130
|
- **Content Items**: Use internal IDs to reference content items
|
|
@@ -143,4 +145,12 @@ When working with taxonomy elements, always retrieve and understand the taxonomy
|
|
|
143
145
|
- Debugging and logging
|
|
144
146
|
- Initial content setup when IDs are not yet known
|
|
145
147
|
|
|
146
|
-
All MCP tools have been optimized to work with internal IDs for maximum efficiency
|
|
148
|
+
All MCP tools have been optimized to work with internal IDs for maximum efficiency.
|
|
149
|
+
|
|
150
|
+
### Content Search Tools
|
|
151
|
+
|
|
152
|
+
The MCP server provides two search tools with distinct purposes:
|
|
153
|
+
- **filter-variants-mapi**: Exact keyword matching with advanced filtering capabilities
|
|
154
|
+
- **search-variants-mapi**: AI-powered semantic/conceptual search (when available)
|
|
155
|
+
|
|
156
|
+
See each tool's description for detailed usage guidelines and selection criteria.`;
|
|
@@ -2,7 +2,7 @@ import { z } from "zod";
|
|
|
2
2
|
import { createMapiClient } from "../clients/kontentClients.js";
|
|
3
3
|
import { handleMcpToolError } from "../utils/errorHandler.js";
|
|
4
4
|
import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
|
|
5
|
-
export const registerTool = (server) => {
|
|
5
|
+
export const registerTool = (server, config) => {
|
|
6
6
|
server.tool("create-variant-version-mapi", "Create new version of Kontent.ai language variant via Management API. This operation creates a new version of an existing language variant, useful for content versioning and creating new drafts from published content.", {
|
|
7
7
|
itemId: z
|
|
8
8
|
.string()
|
|
@@ -13,7 +13,7 @@ export const registerTool = (server) => {
|
|
|
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
15
|
}, async ({ itemId, languageId }, { authInfo: { token, clientId } = {} }) => {
|
|
16
|
-
const client = createMapiClient(clientId, token);
|
|
16
|
+
const client = createMapiClient(clientId, token, config);
|
|
17
17
|
try {
|
|
18
18
|
const response = await client
|
|
19
19
|
.createNewVersionOfLanguageVariant()
|
|
@@ -22,7 +22,7 @@ export const registerTool = (server) => {
|
|
|
22
22
|
.toPromise();
|
|
23
23
|
return createMcpToolSuccessResponse({
|
|
24
24
|
message: `Successfully created new version of language variant '${languageId}' for content item '${itemId}'`,
|
|
25
|
-
result: response.
|
|
25
|
+
result: response.rawData,
|
|
26
26
|
});
|
|
27
27
|
}
|
|
28
28
|
catch (error) {
|