@kontent-ai/mcp-server 0.18.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 +12 -0
- package/build/clients/kontentClients.js +10 -7
- package/build/config/appConfiguration.js +2 -2
- package/build/schemas/searchOperationSchemas.js +14 -0
- package/build/server.js +2 -2
- package/build/telemetry/applicationInsights.js +157 -0
- package/build/telemetry/telemetrySanitizer.js +48 -0
- package/build/tools/add-taxonomy-group-mapi.js +1 -1
- package/build/tools/change-variant-workflow-step-mapi.js +1 -1
- package/build/tools/context/initial-context.js +11 -1
- package/build/tools/create-variant-version-mapi.js +1 -1
- package/build/tools/delete-content-item-mapi.js +1 -1
- package/build/tools/delete-content-type-mapi.js +1 -1
- package/build/tools/delete-language-variant-mapi.js +1 -1
- package/build/tools/filter-variants-mapi.js +22 -44
- package/build/tools/get-asset-mapi.js +1 -1
- package/build/tools/get-item-mapi.js +1 -1
- 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/get-variant-mapi.js +1 -1
- package/build/tools/list-assets-mapi.js +2 -1
- package/build/tools/list-content-type-snippets-mapi.js +2 -1
- package/build/tools/list-content-types-mapi.js +2 -1
- package/build/tools/list-languages-mapi.js +2 -1
- package/build/tools/list-taxonomy-groups-mapi.js +2 -1
- package/build/tools/list-workflows-mapi.js +1 -1
- package/build/tools/search-variants-mapi.js +95 -0
- package/build/utils/errorHandler.js +2 -1
- package/package.json +2 -2
- package/build/tools/get-item-dapi.js +0 -26
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
|
@@ -6,6 +6,7 @@ import express from "express";
|
|
|
6
6
|
import packageJson from "../package.json" with { type: "json" };
|
|
7
7
|
import { loadAppConfiguration, } from "./config/appConfiguration.js";
|
|
8
8
|
import { createServer } from "./server.js";
|
|
9
|
+
import { initializeApplicationInsights, trackException, trackServerStartup, } from "./telemetry/applicationInsights.js";
|
|
9
10
|
import { extractBearerToken } from "./utils/extractBearerToken.js";
|
|
10
11
|
import { isValidGuid } from "./utils/isValidGuid.js";
|
|
11
12
|
const version = packageJson.version;
|
|
@@ -28,6 +29,7 @@ async function startStreamableHTTP(config) {
|
|
|
28
29
|
}
|
|
29
30
|
catch (error) {
|
|
30
31
|
console.error("Error handling MCP request:", error);
|
|
32
|
+
trackException(error, "MCP Request Handler");
|
|
31
33
|
if (!res.headersSent) {
|
|
32
34
|
res.status(500).json({
|
|
33
35
|
jsonrpc: "2.0",
|
|
@@ -96,6 +98,7 @@ async function startStreamableHTTP(config) {
|
|
|
96
98
|
}
|
|
97
99
|
catch (error) {
|
|
98
100
|
console.error("Error handling MCP request:", error);
|
|
101
|
+
trackException(error, "MCP Multi-tenant Request Handler");
|
|
99
102
|
if (!res.headersSent) {
|
|
100
103
|
res.status(500).json({
|
|
101
104
|
jsonrpc: "2.0",
|
|
@@ -128,6 +131,10 @@ async function startStreamableHTTP(config) {
|
|
|
128
131
|
id: null,
|
|
129
132
|
}));
|
|
130
133
|
});
|
|
134
|
+
app.use((err, _req, _res, next) => {
|
|
135
|
+
trackException(err, "Express Error Handler");
|
|
136
|
+
next(err);
|
|
137
|
+
});
|
|
131
138
|
const PORT = process.env.PORT || 3001;
|
|
132
139
|
app.listen(PORT, () => {
|
|
133
140
|
console.log(`Kontent.ai MCP Server v${version} (Streamable HTTP) running on port ${PORT}.
|
|
@@ -144,6 +151,10 @@ async function startStdio(config) {
|
|
|
144
151
|
}
|
|
145
152
|
async function main() {
|
|
146
153
|
const config = await loadAppConfiguration();
|
|
154
|
+
if (config) {
|
|
155
|
+
initializeApplicationInsights(config);
|
|
156
|
+
trackServerStartup(version);
|
|
157
|
+
}
|
|
147
158
|
const args = process.argv.slice(2);
|
|
148
159
|
const transportType = args[0]?.toLowerCase();
|
|
149
160
|
if (!transportType ||
|
|
@@ -160,5 +171,6 @@ async function main() {
|
|
|
160
171
|
}
|
|
161
172
|
main().catch((error) => {
|
|
162
173
|
console.error("Fatal error:", error);
|
|
174
|
+
trackException(error, "Server Startup");
|
|
163
175
|
process.exit(1);
|
|
164
176
|
});
|
|
@@ -7,9 +7,17 @@ const sourceTrackingHeaderName = "X-KC-SOURCE";
|
|
|
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
9
|
* @param config Optional configuration object
|
|
10
|
+
* @param additionalHeaders Optional additional headers to include in requests
|
|
10
11
|
* @returns Management API client instance
|
|
11
12
|
*/
|
|
12
|
-
export const createMapiClient = (environmentId, apiKey, config) => {
|
|
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
|
+
];
|
|
13
21
|
return createManagementClient({
|
|
14
22
|
apiKey: apiKey ??
|
|
15
23
|
process.env.KONTENT_API_KEY ??
|
|
@@ -17,12 +25,7 @@ export const createMapiClient = (environmentId, apiKey, config) => {
|
|
|
17
25
|
environmentId: environmentId ??
|
|
18
26
|
process.env.KONTENT_ENVIRONMENT_ID ??
|
|
19
27
|
throwError("KONTENT_ENVIRONMENT_ID is not set"),
|
|
20
|
-
headers: [
|
|
21
|
-
{
|
|
22
|
-
header: sourceTrackingHeaderName,
|
|
23
|
-
value: `${packageJson.name};${packageJson.version}`,
|
|
24
|
-
},
|
|
25
|
-
],
|
|
26
28
|
baseUrl: config ? `${config.manageApiUrl}v2` : undefined,
|
|
29
|
+
headers: allHeaders,
|
|
27
30
|
});
|
|
28
31
|
};
|
|
@@ -30,7 +30,6 @@ export async function loadAppConfiguration() {
|
|
|
30
30
|
{ keyFilter: "ApplicationInsights:*", labelFilter: label },
|
|
31
31
|
{ keyFilter: "Global:Runtime:*", labelFilter: label },
|
|
32
32
|
{ keyFilter: "Draft:ManageApi:*", labelFilter: label },
|
|
33
|
-
{ keyFilter: "Deliver:ApiClient:Domains:*", labelFilter: label },
|
|
34
33
|
]);
|
|
35
34
|
const credential = new DefaultAzureCredential();
|
|
36
35
|
const configMap = await load(appConfigEndpoint, credential, {
|
|
@@ -40,8 +39,9 @@ export async function loadAppConfiguration() {
|
|
|
40
39
|
},
|
|
41
40
|
});
|
|
42
41
|
return {
|
|
42
|
+
applicationInsightsConnectionString: getConfigValue(configMap, "ApplicationInsights:ConnectionString"),
|
|
43
|
+
projectLocation: getConfigValue(configMap, "Global:Runtime:ProjectLocation"),
|
|
43
44
|
manageApiUrl: getConfigValue(configMap, "Draft:ManageApi:Url"),
|
|
44
|
-
deliveryApiUrl: getConfigValue(configMap, "Deliver:ApiClient:Domains:LiveContentDomain"),
|
|
45
45
|
};
|
|
46
46
|
}
|
|
47
47
|
catch (error) {
|
|
@@ -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,6 +25,7 @@ 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";
|
|
@@ -42,7 +42,6 @@ export const createServer = (config) => {
|
|
|
42
42
|
// Register all tools
|
|
43
43
|
registerGetInitialContext(server);
|
|
44
44
|
registerGetItemMapi(server, config);
|
|
45
|
-
registerGetItemDapi(server, config);
|
|
46
45
|
registerGetVariantMapi(server, config);
|
|
47
46
|
registerGetTypeMapi(server, config);
|
|
48
47
|
registerListContentTypesMapi(server, config);
|
|
@@ -67,6 +66,7 @@ export const createServer = (config) => {
|
|
|
67
66
|
registerListWorkflowsMapi(server, config);
|
|
68
67
|
registerChangeVariantWorkflowStepMapi(server, config);
|
|
69
68
|
registerFilterVariantsMapi(server, config);
|
|
69
|
+
registerSearchVariantsMapi(server, config);
|
|
70
70
|
registerPublishVariantMapi(server, config);
|
|
71
71
|
registerUnpublishVariantMapi(server, config);
|
|
72
72
|
return { server };
|
|
@@ -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
|
+
}
|
|
@@ -10,7 +10,7 @@ export const registerTool = (server, config) => {
|
|
|
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");
|
|
@@ -38,7 +38,7 @@ export const registerTool = (server, config) => {
|
|
|
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.`;
|
|
@@ -22,7 +22,7 @@ export const registerTool = (server, config) => {
|
|
|
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) {
|
|
@@ -14,7 +14,7 @@ export const registerTool = (server, config) => {
|
|
|
14
14
|
.toPromise();
|
|
15
15
|
return createMcpToolSuccessResponse({
|
|
16
16
|
message: `Content item '${id}' deleted successfully`,
|
|
17
|
-
deletedItem: response.
|
|
17
|
+
deletedItem: response.rawData,
|
|
18
18
|
});
|
|
19
19
|
}
|
|
20
20
|
catch (error) {
|
|
@@ -14,7 +14,7 @@ export const registerTool = (server, config) => {
|
|
|
14
14
|
.toPromise();
|
|
15
15
|
return createMcpToolSuccessResponse({
|
|
16
16
|
message: `Content type '${codename}' deleted successfully`,
|
|
17
|
-
deletedType: response.
|
|
17
|
+
deletedType: response.rawData,
|
|
18
18
|
});
|
|
19
19
|
}
|
|
20
20
|
catch (error) {
|
|
@@ -18,7 +18,7 @@ export const registerTool = (server, config) => {
|
|
|
18
18
|
.toPromise();
|
|
19
19
|
return createMcpToolSuccessResponse({
|
|
20
20
|
message: `Language variant '${languageId}' of content item '${itemId}' deleted successfully`,
|
|
21
|
-
deletedVariant: response.
|
|
21
|
+
deletedVariant: response.rawData,
|
|
22
22
|
});
|
|
23
23
|
}
|
|
24
24
|
catch (error) {
|
|
@@ -1,10 +1,25 @@
|
|
|
1
|
+
import { createMapiClient } from "../clients/kontentClients.js";
|
|
1
2
|
import { filterVariantsSchema } from "../schemas/filterVariantSchemas.js";
|
|
2
3
|
import { handleMcpToolError } from "../utils/errorHandler.js";
|
|
3
4
|
import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
|
|
4
5
|
import { throwError } from "../utils/throwError.js";
|
|
5
6
|
export const registerTool = (server, config) => {
|
|
6
|
-
server.tool("filter-variants-mapi",
|
|
7
|
+
server.tool("filter-variants-mapi", `Filter Kontent.ai language variants of content items using Management API.
|
|
8
|
+
|
|
9
|
+
USE FOR:
|
|
10
|
+
- EXACT keyword matching: finding specific words, phrases, names, codes, or IDs in content. Example: 'find items containing rabbit' → search 'rabbit'
|
|
11
|
+
- Advanced filtering by content type, contributors, workflow steps, taxonomies etc
|
|
12
|
+
- CAN expand concepts to keywords when using filter (e.g., "neurology-related" → "neurology neurological brain nervous system")
|
|
13
|
+
- Also use as fallback when AI search is unavailable`, filterVariantsSchema.shape, async ({ search_phrase, content_types, contributors, has_no_contributors, completion_statuses, language, workflow_steps, taxonomy_groups, order_by, order_direction, continuation_token, }, { authInfo: { token, clientId } = {} }) => {
|
|
7
14
|
try {
|
|
15
|
+
const environmentId = clientId ?? process.env.KONTENT_ENVIRONMENT_ID;
|
|
16
|
+
if (!environmentId) {
|
|
17
|
+
throwError("Missing required environment ID");
|
|
18
|
+
}
|
|
19
|
+
const additionalHeaders = continuation_token
|
|
20
|
+
? [{ header: "X-Continuation", value: continuation_token }]
|
|
21
|
+
: undefined;
|
|
22
|
+
const client = createMapiClient(environmentId, token, config, additionalHeaders);
|
|
8
23
|
const requestPayload = {
|
|
9
24
|
filters: {
|
|
10
25
|
search_phrase,
|
|
@@ -23,49 +38,12 @@ export const registerTool = (server, config) => {
|
|
|
23
38
|
}
|
|
24
39
|
: null,
|
|
25
40
|
};
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
throwError("Missing required API key");
|
|
33
|
-
}
|
|
34
|
-
const baseUrl = config
|
|
35
|
-
? `${config.manageApiUrl}`
|
|
36
|
-
: `https://manage.kontent.ai/`;
|
|
37
|
-
const url = `${baseUrl}v2/projects/${environmentId}/early-access/variants/filter`;
|
|
38
|
-
const headers = {
|
|
39
|
-
Authorization: `Bearer ${apiKey}`,
|
|
40
|
-
"Content-Type": "application/json",
|
|
41
|
-
};
|
|
42
|
-
if (continuation_token) {
|
|
43
|
-
headers["X-Continuation"] = continuation_token;
|
|
44
|
-
}
|
|
45
|
-
const response = await fetch(url, {
|
|
46
|
-
method: "POST",
|
|
47
|
-
headers: headers,
|
|
48
|
-
body: JSON.stringify(requestPayload),
|
|
49
|
-
});
|
|
50
|
-
if (!response.ok) {
|
|
51
|
-
const responseText = await response.text();
|
|
52
|
-
let responseData;
|
|
53
|
-
try {
|
|
54
|
-
responseData = JSON.parse(responseText);
|
|
55
|
-
}
|
|
56
|
-
catch {
|
|
57
|
-
responseData = responseText;
|
|
58
|
-
}
|
|
59
|
-
const error = new Error(`HTTP error! status: ${response.status}`);
|
|
60
|
-
error.response = {
|
|
61
|
-
status: response.status,
|
|
62
|
-
statusText: response.statusText,
|
|
63
|
-
data: responseData,
|
|
64
|
-
};
|
|
65
|
-
throw error;
|
|
66
|
-
}
|
|
67
|
-
const responseData = await response.json();
|
|
68
|
-
return createMcpToolSuccessResponse(responseData);
|
|
41
|
+
const response = await client
|
|
42
|
+
.post()
|
|
43
|
+
.withAction(`projects/${environmentId}/early-access/variants/filter`)
|
|
44
|
+
.withData(requestPayload)
|
|
45
|
+
.toPromise();
|
|
46
|
+
return createMcpToolSuccessResponse(response.data);
|
|
69
47
|
}
|
|
70
48
|
catch (error) {
|
|
71
49
|
return handleMcpToolError(error, "Variant Search");
|
|
@@ -12,7 +12,7 @@ export const registerTool = (server, config) => {
|
|
|
12
12
|
.viewAsset()
|
|
13
13
|
.byAssetId(assetId)
|
|
14
14
|
.toPromise();
|
|
15
|
-
return createMcpToolSuccessResponse(response.
|
|
15
|
+
return createMcpToolSuccessResponse(response.rawData);
|
|
16
16
|
}
|
|
17
17
|
catch (error) {
|
|
18
18
|
return handleMcpToolError(error, "Asset Retrieval");
|
|
@@ -12,7 +12,7 @@ export const registerTool = (server, config) => {
|
|
|
12
12
|
.viewContentItem()
|
|
13
13
|
.byItemId(id)
|
|
14
14
|
.toPromise();
|
|
15
|
-
return createMcpToolSuccessResponse(response.
|
|
15
|
+
return createMcpToolSuccessResponse(response.rawData);
|
|
16
16
|
}
|
|
17
17
|
catch (error) {
|
|
18
18
|
return handleMcpToolError(error, "Item Retrieval");
|
|
@@ -12,7 +12,7 @@ export const registerTool = (server, config) => {
|
|
|
12
12
|
.getTaxonomy()
|
|
13
13
|
.byTaxonomyId(id)
|
|
14
14
|
.toPromise();
|
|
15
|
-
return createMcpToolSuccessResponse(response.
|
|
15
|
+
return createMcpToolSuccessResponse(response.rawData);
|
|
16
16
|
}
|
|
17
17
|
catch (error) {
|
|
18
18
|
return handleMcpToolError(error, "Taxonomy Group Retrieval");
|
|
@@ -12,7 +12,7 @@ export const registerTool = (server, config) => {
|
|
|
12
12
|
.viewContentType()
|
|
13
13
|
.byTypeId(id)
|
|
14
14
|
.toPromise();
|
|
15
|
-
return createMcpToolSuccessResponse(response.
|
|
15
|
+
return createMcpToolSuccessResponse(response.rawData);
|
|
16
16
|
}
|
|
17
17
|
catch (error) {
|
|
18
18
|
return handleMcpToolError(error, "Content Type Retrieval");
|
|
@@ -12,7 +12,7 @@ export const registerTool = (server, config) => {
|
|
|
12
12
|
.viewContentTypeSnippet()
|
|
13
13
|
.byTypeId(id)
|
|
14
14
|
.toPromise();
|
|
15
|
-
return createMcpToolSuccessResponse(response.
|
|
15
|
+
return createMcpToolSuccessResponse(response.rawData);
|
|
16
16
|
}
|
|
17
17
|
catch (error) {
|
|
18
18
|
return handleMcpToolError(error, "Content Type Snippet Retrieval");
|
|
@@ -16,7 +16,7 @@ export const registerTool = (server, config) => {
|
|
|
16
16
|
.byItemId(itemId)
|
|
17
17
|
.byLanguageId(languageId)
|
|
18
18
|
.toPromise();
|
|
19
|
-
return createMcpToolSuccessResponse(response.
|
|
19
|
+
return createMcpToolSuccessResponse(response.rawData);
|
|
20
20
|
}
|
|
21
21
|
catch (error) {
|
|
22
22
|
return handleMcpToolError(error, "Language Variant Retrieval");
|
|
@@ -6,7 +6,8 @@ export const registerTool = (server, config) => {
|
|
|
6
6
|
const client = createMapiClient(clientId, token, config);
|
|
7
7
|
try {
|
|
8
8
|
const response = await client.listAssets().toAllPromise();
|
|
9
|
-
|
|
9
|
+
const rawData = response.responses.flatMap((r) => r.rawData.assets);
|
|
10
|
+
return createMcpToolSuccessResponse(rawData);
|
|
10
11
|
}
|
|
11
12
|
catch (error) {
|
|
12
13
|
return handleMcpToolError(error, "Assets Listing");
|
|
@@ -6,7 +6,8 @@ export const registerTool = (server, config) => {
|
|
|
6
6
|
const client = createMapiClient(clientId, token, config);
|
|
7
7
|
try {
|
|
8
8
|
const response = await client.listContentTypeSnippets().toAllPromise();
|
|
9
|
-
|
|
9
|
+
const rawData = response.responses.flatMap((r) => r.rawData.snippets);
|
|
10
|
+
return createMcpToolSuccessResponse(rawData);
|
|
10
11
|
}
|
|
11
12
|
catch (error) {
|
|
12
13
|
return handleMcpToolError(error, "Content Type Snippets Listing");
|
|
@@ -6,7 +6,8 @@ export const registerTool = (server, config) => {
|
|
|
6
6
|
const client = createMapiClient(clientId, token, config);
|
|
7
7
|
try {
|
|
8
8
|
const response = await client.listContentTypes().toAllPromise();
|
|
9
|
-
|
|
9
|
+
const rawData = response.responses.flatMap((r) => r.rawData.types);
|
|
10
|
+
return createMcpToolSuccessResponse(rawData);
|
|
10
11
|
}
|
|
11
12
|
catch (error) {
|
|
12
13
|
return handleMcpToolError(error, "Content Types Listing");
|
|
@@ -6,7 +6,8 @@ export const registerTool = (server, config) => {
|
|
|
6
6
|
const client = createMapiClient(clientId, token, config);
|
|
7
7
|
try {
|
|
8
8
|
const response = await client.listLanguages().toAllPromise();
|
|
9
|
-
|
|
9
|
+
const rawData = response.responses.flatMap((r) => r.rawData.languages);
|
|
10
|
+
return createMcpToolSuccessResponse(rawData);
|
|
10
11
|
}
|
|
11
12
|
catch (error) {
|
|
12
13
|
return handleMcpToolError(error, "Languages Listing");
|
|
@@ -6,7 +6,8 @@ export const registerTool = (server, config) => {
|
|
|
6
6
|
const client = createMapiClient(clientId, token, config);
|
|
7
7
|
try {
|
|
8
8
|
const response = await client.listTaxonomies().toAllPromise();
|
|
9
|
-
|
|
9
|
+
const rawData = response.responses.flatMap((r) => r.rawData.taxonomies);
|
|
10
|
+
return createMcpToolSuccessResponse(rawData);
|
|
10
11
|
}
|
|
11
12
|
catch (error) {
|
|
12
13
|
return handleMcpToolError(error, "Taxonomy Groups Listing");
|
|
@@ -6,7 +6,7 @@ export const registerTool = (server, config) => {
|
|
|
6
6
|
const client = createMapiClient(clientId, token, config);
|
|
7
7
|
try {
|
|
8
8
|
const response = await client.listWorkflows().toPromise();
|
|
9
|
-
return createMcpToolSuccessResponse(response.
|
|
9
|
+
return createMcpToolSuccessResponse(response.rawData);
|
|
10
10
|
}
|
|
11
11
|
catch (error) {
|
|
12
12
|
return handleMcpToolError(error, "Workflows Listing");
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import pRetry from "p-retry";
|
|
2
|
+
import { createMapiClient } from "../clients/kontentClients.js";
|
|
3
|
+
import { searchOperationSchema } from "../schemas/searchOperationSchemas.js";
|
|
4
|
+
import { handleMcpToolError } from "../utils/errorHandler.js";
|
|
5
|
+
import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
|
|
6
|
+
import { throwError } from "../utils/throwError.js";
|
|
7
|
+
export const registerTool = (server, config) => {
|
|
8
|
+
server.tool("search-variants-mapi", `AI-powered semantic search for finding Kontent.ai content by meaning, concepts, themes, and content similarity in a specific language variant. This tool uses vector database and AI to enable searching by meaning and similarity rather than exact keyword matching.
|
|
9
|
+
|
|
10
|
+
CRITICAL REQUIREMENTS:
|
|
11
|
+
- The AI search feature may not be available for all Kontent.ai environments
|
|
12
|
+
- If you receive an "unavailable" status response, DO NOT attempt to use this tool again in the same session
|
|
13
|
+
- Use filter-variants-mapi for exact text matching when semantic search is unavailable
|
|
14
|
+
- Requires language variant filter parameter (e.g., default language '00000000-0000-0000-0000-000000000000')
|
|
15
|
+
- The tool always RETURNS ONLY TOP 50 most relevant items at max
|
|
16
|
+
- Limited filtering options (only by variant ID) - use filter-variants-mapi for advanced filtering by content types, workflow steps, taxonomies, etc.
|
|
17
|
+
|
|
18
|
+
USE FOR:
|
|
19
|
+
- Conceptual search: NEVER extract keywords, pass the single concept/theme as-is (e.g., "find content about keeping beverages cold" → searchPhrase: "beverage coolers")
|
|
20
|
+
- Finding content about topics: Use topic as-is (e.g., "find fairy tales" → searchPhrase: "fairy tales")
|
|
21
|
+
- Content similarity: Find articles similar to a given topic or story theme (e.g. "<<larger piece of content to search similar items>>" -> searchPhrase: "<<larger piece of content to search similar items>>")
|
|
22
|
+
- Thematic content discovery based on meaning and context`, searchOperationSchema.shape, async ({ searchPhrase, filter }, { authInfo: { token, clientId } = {} }) => {
|
|
23
|
+
try {
|
|
24
|
+
const environmentId = clientId ?? process.env.KONTENT_ENVIRONMENT_ID;
|
|
25
|
+
if (!environmentId) {
|
|
26
|
+
throwError("Missing required environment ID");
|
|
27
|
+
}
|
|
28
|
+
const client = createMapiClient(environmentId, token, config);
|
|
29
|
+
// Step 1: Initiate the AI search operation
|
|
30
|
+
const searchPayload = {
|
|
31
|
+
actionName: "Search",
|
|
32
|
+
type: "multiple-inputs-request-v1",
|
|
33
|
+
inputs: {
|
|
34
|
+
searchPhrase: {
|
|
35
|
+
type: "string",
|
|
36
|
+
value: searchPhrase,
|
|
37
|
+
},
|
|
38
|
+
filter: {
|
|
39
|
+
type: "content-item-variant-filter",
|
|
40
|
+
value: filter,
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
trackingData: {
|
|
44
|
+
type: "empty-operation-tracking-data-v1",
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
let searchResponse;
|
|
48
|
+
try {
|
|
49
|
+
searchResponse = await client
|
|
50
|
+
.post()
|
|
51
|
+
.withAction(`projects/${environmentId}/early-access/ai-operation`)
|
|
52
|
+
.withData(searchPayload)
|
|
53
|
+
.toPromise();
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
if (error?.response?.status === 403 &&
|
|
57
|
+
error?.response?.data?.message?.includes("AI Feature Not Available")) {
|
|
58
|
+
return createMcpToolSuccessResponse({
|
|
59
|
+
status: "unavailable",
|
|
60
|
+
result: `AI search feature is not available for environment ${clientId}`,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
const operationData = searchResponse.data;
|
|
66
|
+
const operationId = operationData.operationId;
|
|
67
|
+
// Step 2: Poll for results with exponential backoff
|
|
68
|
+
const resultData = await pRetry(async () => {
|
|
69
|
+
const pollResponse = await client
|
|
70
|
+
.get()
|
|
71
|
+
.withAction(`projects/${environmentId}/early-access/ai-operation/${operationId}`)
|
|
72
|
+
.toPromise();
|
|
73
|
+
const data = pollResponse.data;
|
|
74
|
+
if (data.message.includes("still in progress")) {
|
|
75
|
+
throw new Error("Operation still in progress");
|
|
76
|
+
}
|
|
77
|
+
return data;
|
|
78
|
+
}, {
|
|
79
|
+
retries: 10,
|
|
80
|
+
minTimeout: 1000,
|
|
81
|
+
maxTimeout: 10000,
|
|
82
|
+
factor: 1.5,
|
|
83
|
+
});
|
|
84
|
+
if (resultData.message.includes("completed successfully")) {
|
|
85
|
+
return createMcpToolSuccessResponse({
|
|
86
|
+
result: resultData.result,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
throw new Error(`Search operation error: ${resultData.message}. Operation ID: ${operationId}`);
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
return handleMcpToolError(error, "AI-powered Variant Search");
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Utility for handling errors in MCP tools and returning standardized error responses
|
|
3
3
|
*/
|
|
4
|
+
import { trackException } from "../telemetry/applicationInsights.js";
|
|
4
5
|
/**
|
|
5
6
|
* Handles various types of errors and returns a standardized MCP tool error response
|
|
6
7
|
* @param error The error to handle
|
|
@@ -9,7 +10,7 @@
|
|
|
9
10
|
*/
|
|
10
11
|
export const handleMcpToolError = (error, context) => {
|
|
11
12
|
const contextPrefix = context ? `${context}: ` : "";
|
|
12
|
-
|
|
13
|
+
trackException(error, context);
|
|
13
14
|
if (error?.name === "ContentManagementBaseKontentError" || error?.requestId) {
|
|
14
15
|
const errorMessage = [
|
|
15
16
|
`${contextPrefix}Kontent.ai Management API Error:`,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kontent-ai/mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"build": "rimraf build && tsc",
|
|
@@ -24,12 +24,12 @@
|
|
|
24
24
|
"@azure/app-configuration": "^1.9.0",
|
|
25
25
|
"@azure/app-configuration-provider": "^2.2.0",
|
|
26
26
|
"@azure/identity": "^4.11.1",
|
|
27
|
-
"@kontent-ai/delivery-sdk": "^16.2.0",
|
|
28
27
|
"@kontent-ai/management-sdk": "^7.9.0",
|
|
29
28
|
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
30
29
|
"applicationinsights": "^2.9.8",
|
|
31
30
|
"dotenv": "^16.5.0",
|
|
32
31
|
"express": "^5.1.0",
|
|
32
|
+
"p-retry": "^7.0.0",
|
|
33
33
|
"zod": "^3.25.30"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { createDeliveryClient } from "@kontent-ai/delivery-sdk";
|
|
2
|
-
import { z } from "zod";
|
|
3
|
-
import { handleMcpToolError } from "../utils/errorHandler.js";
|
|
4
|
-
import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";
|
|
5
|
-
export const registerTool = (server, config) => {
|
|
6
|
-
server.tool("get-item-dapi", "Get Kontent.ai item by codename from Delivery API", {
|
|
7
|
-
codename: z.string().describe("Codename of the item to get"),
|
|
8
|
-
environmentId: z
|
|
9
|
-
.string()
|
|
10
|
-
.describe("Environment ID of the item's environment"),
|
|
11
|
-
}, async ({ codename, environmentId }) => {
|
|
12
|
-
const client = createDeliveryClient({
|
|
13
|
-
environmentId,
|
|
14
|
-
proxy: {
|
|
15
|
-
baseUrl: config ? `https://${config.deliveryApiUrl}` : undefined,
|
|
16
|
-
},
|
|
17
|
-
});
|
|
18
|
-
try {
|
|
19
|
-
const response = await client.item(codename).toPromise();
|
|
20
|
-
return createMcpToolSuccessResponse(response.data.item);
|
|
21
|
-
}
|
|
22
|
-
catch (error) {
|
|
23
|
-
return handleMcpToolError(error, "Item Retrieval (Delivery API)");
|
|
24
|
-
}
|
|
25
|
-
});
|
|
26
|
-
};
|