Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,8 @@ npx @kontent-ai/mcp-server@latest shttp
* **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
* **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
* **delete-language-variant-mapi** – Delete Kontent.ai language variant from Management API
* **filter-variants-mapi** – Search and filter Kontent.ai language variants of content items using Management API
* **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.)
* **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)

### Asset Management

Expand Down Expand Up @@ -345,4 +346,4 @@ MIT
[discord-shield]: https://img.shields.io/discord/821885171984891914?color=%237289DA&label=Kontent.ai%20Discord&logo=discord&style=for-the-badge
[discord-url]: https://discord.com/invite/SKCxwPtevJ
[npm-url]: https://www.npmjs.com/package/@kontent-ai/mcp-server
[npm-shield]: https://img.shields.io/npm/v/%40kontent-ai%2Fmcp-server?style=for-the-badge&logo=npm&color=%23CB0000
[npm-shield]: https://img.shields.io/npm/v/%40kontent-ai%2Fmcp-server?style=for-the-badge&logo=npm&color=%23CB0000
32 changes: 30 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@kontent-ai/mcp-server",
"version": "0.18.0",
"version": "0.19.0",
"type": "module",
"scripts": {
"build": "rimraf build && tsc",
Expand Down Expand Up @@ -29,6 +29,7 @@
"applicationinsights": "^2.9.8",
"dotenv": "^16.5.0",
"express": "^5.1.0",
"p-retry": "^7.0.0",
"zod": "^3.25.30"
},
"devDependencies": {
Expand Down
17 changes: 11 additions & 6 deletions src/clients/kontentClients.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,23 @@ const sourceTrackingHeaderName = "X-KC-SOURCE";
* @param environmentId Optional environment ID (defaults to process.env.KONTENT_ENVIRONMENT_ID)
* @param apiKey Optional API key (defaults to process.env.KONTENT_API_KEY)
* @param config Optional configuration object
* @param additionalHeaders Optional additional headers to include in requests
* @returns Management API client instance
*/
export const createMapiClient = (
environmentId?: string,
apiKey?: string,
config?: Pick<AppConfiguration, "manageApiUrl"> | null,
additionalHeaders?: Array<{ header: string; value: string }>,
) => {
const allHeaders = [
{
header: sourceTrackingHeaderName,
value: `${packageJson.name};${packageJson.version}`,
},
...(additionalHeaders || []),
];

return createManagementClient({
apiKey:
apiKey ??
Expand All @@ -26,12 +36,7 @@ export const createMapiClient = (
environmentId ??
process.env.KONTENT_ENVIRONMENT_ID ??
throwError("KONTENT_ENVIRONMENT_ID is not set"),
headers: [
{
header: sourceTrackingHeaderName,
value: `${packageJson.name};${packageJson.version}`,
},
],
baseUrl: config ? `${config.manageApiUrl}v2` : undefined,
headers: allHeaders,
});
};
19 changes: 19 additions & 0 deletions src/schemas/searchOperationSchemas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { z } from "zod";

export const searchOperationSchema = z.object({
searchPhrase: z
.string()
.describe(
"Search phrase for AI-powered semantic search. Uses vector database to find content by meaning and similarity, not just exact keyword matching",
),
filter: z
.object({
variantId: z
.string()
.uuid()
.describe("UUID of the language variant to filter by"),
})
.describe(
"Mandatory content item variant filter to restrict search scope. Must specify a valid variant UUID",
),
});
2 changes: 2 additions & 0 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { registerTool as registerListTaxonomyGroupsMapi } from "./tools/list-tax
import { registerTool as registerListWorkflowsMapi } from "./tools/list-workflows-mapi.js";
import { registerTool as registerPatchContentTypeMapi } from "./tools/patch-content-type-mapi.js";
import { registerTool as registerPublishVariantMapi } from "./tools/publish-variant-mapi.js";
import { registerTool as registerSearchVariantsMapi } from "./tools/search-variants-mapi.js";
import { registerTool as registerUnpublishVariantMapi } from "./tools/unpublish-variant-mapi.js";
import { registerTool as registerUpdateContentItemMapi } from "./tools/update-content-item-mapi.js";
import { registerTool as registerUpsertLanguageVariantMapi } from "./tools/upsert-language-variant-mapi.js";
Expand Down Expand Up @@ -68,6 +69,7 @@ export const createServer = (config: AppConfiguration | null) => {
registerListWorkflowsMapi(server, config);
registerChangeVariantWorkflowStepMapi(server, config);
registerFilterVariantsMapi(server, config);
registerSearchVariantsMapi(server, config);
registerPublishVariantMapi(server, config);
registerUnpublishVariantMapi(server, config);

Expand Down
12 changes: 11 additions & 1 deletion src/tools/context/initial-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ When working with taxonomy elements, always retrieve and understand the taxonomy

## MCP Tool Usage Guidelines

### ID Reference Preferences

**CRITICAL**: When using MCP tools, always prefer internal IDs over codenames:

- **Content Items**: Use internal IDs to reference content items
Expand All @@ -143,4 +145,12 @@ When working with taxonomy elements, always retrieve and understand the taxonomy
- Debugging and logging
- Initial content setup when IDs are not yet known

All MCP tools have been optimized to work with internal IDs for maximum efficiency.`;
All MCP tools have been optimized to work with internal IDs for maximum efficiency.

### Content Search Tools

The MCP server provides two search tools with distinct purposes:
- **filter-variants-mapi**: Exact keyword matching with advanced filtering capabilities
- **search-variants-mapi**: AI-powered semantic/conceptual search (when available)

See each tool's description for detailed usage guidelines and selection criteria.`;
90 changes: 30 additions & 60 deletions src/tools/filter-variants-mapi.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { createMapiClient } from "../clients/kontentClients.js";
import type { AppConfiguration } from "../config/appConfiguration.js";
import { filterVariantsSchema } from "../schemas/filterVariantSchemas.js";
import { handleMcpToolError } from "../utils/errorHandler.js";
Expand All @@ -11,7 +12,13 @@ export const registerTool = (
): void => {
server.tool(
"filter-variants-mapi",
"Search and filter Kontent.ai language variants of content items using Management API",
`Filter Kontent.ai language variants of content items using Management API.

USE FOR:
- EXACT keyword matching: finding specific words, phrases, names, codes, or IDs in content. Example: 'find items containing rabbit' → search 'rabbit'
- Advanced filtering by content type, contributors, workflow steps, taxonomies etc
- CAN expand concepts to keywords when using filter (e.g., "neurology-related" → "neurology neurological brain nervous system")
- Also use as fallback when AI search is unavailable`,
filterVariantsSchema.shape,
async (
{
Expand All @@ -30,6 +37,22 @@ export const registerTool = (
{ authInfo: { token, clientId } = {} },
) => {
try {
const environmentId = clientId ?? process.env.KONTENT_ENVIRONMENT_ID;
if (!environmentId) {
throwError("Missing required environment ID");
}

const additionalHeaders = continuation_token
? [{ header: "X-Continuation", value: continuation_token }]
: undefined;

const client = createMapiClient(
environmentId,
token,
config,
additionalHeaders,
);

const requestPayload = {
filters: {
search_phrase,
Expand All @@ -50,69 +73,16 @@ export const registerTool = (
: null,
};

const environmentId = clientId ?? process.env.KONTENT_ENVIRONMENT_ID;
if (!environmentId) {
throwError("Missing required environment ID");
}
const response = await client
.post()
.withAction(`projects/${environmentId}/early-access/variants/filter`)
.withData(requestPayload)
.toPromise();

const apiKey = token ?? process.env.KONTENT_API_KEY;
if (!apiKey) {
throwError("Missing required API key");
}

const baseUrl = config
? `${config.manageApiUrl}`
: `https://manage.kontent.ai/`;
const url = `${baseUrl}v2/projects/${environmentId}/early-access/variants/filter`;

const headers: Record<string, string> = {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
};
if (continuation_token) {
headers["X-Continuation"] = continuation_token;
}

const response = await fetch(url, {
method: "POST",
headers: headers,
body: JSON.stringify(requestPayload),
});

if (!response.ok) {
const responseText = await response.text();
let responseData: string;
try {
responseData = JSON.parse(responseText);
} catch {
responseData = responseText;
}

const error: HttpError = new Error(
`HTTP error! status: ${response.status}`,
);
error.response = {
status: response.status,
statusText: response.statusText,
data: responseData,
};
throw error;
}

const responseData = await response.json();

return createMcpToolSuccessResponse(responseData);
return createMcpToolSuccessResponse(response.data);
} catch (error: unknown) {
return handleMcpToolError(error, "Variant Search");
}
},
);
};

interface HttpError extends Error {
response?: {
status: number;
statusText: string;
data: any;
};
}
Loading