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: 5 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,11 @@ Required environment variables:
- `KONTENT_ENVIRONMENT_ID`: Environment ID
- `PORT`: Server port (optional, defaults to 3001)

Optional telemetry and configuration variables:
- `appInsightsConnectionString`: Application Insights connection string for telemetry
- `projectLocation`: Project location identifier for telemetry tracking
- `manageApiUrl`: Custom Management API base URL (e.g., for preview environments)

#### Multi-Tenant Mode (Streamable HTTP only)
No environment variables required. Instead:
- Environment ID is provided via URL path: `/{environmentId}/mcp`
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@ For single-tenant mode, configure environment variables:
| KONTENT_API_KEY | Your Kontent.ai Management API key | ✅ |
| KONTENT_ENVIRONMENT_ID | Your environment ID | ✅ |
| PORT | Port for HTTP transport (defaults to 3001) | ❌ |
| appInsightsConnectionString | Application Insights connection string for telemetry | ❌ |
| projectLocation | Project location identifier for telemetry tracking | ❌ |
| manageApiUrl | Custom Management API base URL (for preview environments) | ❌ |

### Multi-Tenant Mode

Expand Down
3 changes: 0 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,6 @@
"author": "Jiri Lojda",
"license": "MIT",
"dependencies": {
"@azure/app-configuration": "^1.9.0",
"@azure/app-configuration-provider": "^2.2.0",
"@azure/identity": "^4.11.1",
"@kontent-ai/management-sdk": "^7.9.0",
"@modelcontextprotocol/sdk": "^1.12.0",
"applicationinsights": "^2.9.8",
Expand Down
25 changes: 9 additions & 16 deletions src/bin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,6 @@ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/
import "dotenv/config";
import express from "express";
import packageJson from "../package.json" with { type: "json" };
import {
type AppConfiguration,
loadAppConfiguration,
} from "./config/appConfiguration.js";
import { createServer } from "./server.js";
import {
initializeApplicationInsights,
Expand All @@ -19,13 +15,13 @@ import { isValidGuid } from "./utils/isValidGuid.js";

const version = packageJson.version;

async function startStreamableHTTP(config: AppConfiguration | null) {
async function startStreamableHTTP() {
const app = express();
app.use(express.json());

app.post("/mcp", async (req, res) => {
try {
const { server } = createServer(config);
const { server } = createServer();
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined,
});
Expand Down Expand Up @@ -88,7 +84,7 @@ async function startStreamableHTTP(config: AppConfiguration | null) {
return;
}

const { server } = createServer(config);
const { server } = createServer();
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined,
});
Expand Down Expand Up @@ -183,19 +179,16 @@ Available endpoints:
});
}

async function startStdio(config: AppConfiguration | null) {
const { server } = createServer(config);
async function startStdio() {
const { server } = createServer();
const transport = new StdioServerTransport();
console.log(`Kontent.ai MCP Server v${version} (stdio) starting`);
await server.connect(transport);
}

async function main() {
const config = await loadAppConfiguration();
if (config) {
initializeApplicationInsights(config);
trackServerStartup(version);
}
initializeApplicationInsights();
trackServerStartup(version);

const args = process.argv.slice(2);
const transportType = args[0]?.toLowerCase();
Expand All @@ -209,9 +202,9 @@ async function main() {
}

if (transportType === "stdio") {
await startStdio(config);
await startStdio();
} else if (transportType === "shttp") {
await startStreamableHTTP(config);
await startStreamableHTTP();
}
}

Expand Down
7 changes: 3 additions & 4 deletions src/clients/kontentClients.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { createManagementClient } from "@kontent-ai/management-sdk";
import packageJson from "../../package.json" with { type: "json" };
import type { AppConfiguration } from "../config/appConfiguration.js";
import { throwError } from "../utils/throwError.js";

const sourceTrackingHeaderName = "X-KC-SOURCE";
Expand All @@ -9,14 +8,12 @@ const sourceTrackingHeaderName = "X-KC-SOURCE";
* Creates a Kontent.ai Management API client
* @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 = [
Expand All @@ -27,6 +24,8 @@ export const createMapiClient = (
...(additionalHeaders || []),
];

const manageApiUrl = process.env.manageApiUrl;

return createManagementClient({
apiKey:
apiKey ??
Expand All @@ -36,7 +35,7 @@ export const createMapiClient = (
environmentId ??
process.env.KONTENT_ENVIRONMENT_ID ??
throwError("KONTENT_ENVIRONMENT_ID is not set"),
baseUrl: config ? `${config.manageApiUrl}v2` : undefined,
baseUrl: manageApiUrl ? `${manageApiUrl}v2` : undefined,
headers: allHeaders,
});
};
71 changes: 0 additions & 71 deletions src/config/appConfiguration.ts

This file was deleted.

59 changes: 29 additions & 30 deletions src/server.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import packageJson from "../package.json" with { type: "json" };
import type { AppConfiguration } from "./config/appConfiguration.js";
import { registerTool as registerAddContentItemMapi } from "./tools/add-content-item-mapi.js";
import { registerTool as registerAddContentTypeMapi } from "./tools/add-content-type-mapi.js";
import { registerTool as registerAddContentTypeSnippetMapi } from "./tools/add-content-type-snippet-mapi.js";
Expand Down Expand Up @@ -32,7 +31,7 @@ import { registerTool as registerUpdateContentItemMapi } from "./tools/update-co
import { registerTool as registerUpsertLanguageVariantMapi } from "./tools/upsert-language-variant-mapi.js";

// Create server instance
export const createServer = (config: AppConfiguration | null) => {
export const createServer = () => {
const server = new McpServer({
name: "kontent-ai",
version: packageJson.version,
Expand All @@ -44,34 +43,34 @@ export const createServer = (config: AppConfiguration | null) => {

// Register all tools
registerGetInitialContext(server);
registerGetItemMapi(server, config);
registerGetVariantMapi(server, config);
registerGetTypeMapi(server, config);
registerListContentTypesMapi(server, config);
registerDeleteContentTypeMapi(server, config);
registerListLanguagesMapi(server, config);
registerGetAssetMapi(server, config);
registerListAssetsMapi(server, config);
registerAddContentTypeMapi(server, config);
registerPatchContentTypeMapi(server, config);
registerAddContentTypeSnippetMapi(server, config);
registerGetTypeSnippetMapi(server, config);
registerListContentTypeSnippetsMapi(server, config);
registerAddTaxonomyGroupMapi(server, config);
registerListTaxonomyGroupsMapi(server, config);
registerGetTaxonomyGroupMapi(server, config);
registerAddContentItemMapi(server, config);
registerUpdateContentItemMapi(server, config);
registerDeleteContentItemMapi(server, config);
registerUpsertLanguageVariantMapi(server, config);
registerCreateVariantVersionMapi(server, config);
registerDeleteLanguageVariantMapi(server, config);
registerListWorkflowsMapi(server, config);
registerChangeVariantWorkflowStepMapi(server, config);
registerFilterVariantsMapi(server, config);
registerSearchVariantsMapi(server, config);
registerPublishVariantMapi(server, config);
registerUnpublishVariantMapi(server, config);
registerGetItemMapi(server);
registerGetVariantMapi(server);
registerGetTypeMapi(server);
registerListContentTypesMapi(server);
registerDeleteContentTypeMapi(server);
registerListLanguagesMapi(server);
registerGetAssetMapi(server);
registerListAssetsMapi(server);
registerAddContentTypeMapi(server);
registerPatchContentTypeMapi(server);
registerAddContentTypeSnippetMapi(server);
registerGetTypeSnippetMapi(server);
registerListContentTypeSnippetsMapi(server);
registerAddTaxonomyGroupMapi(server);
registerListTaxonomyGroupsMapi(server);
registerGetTaxonomyGroupMapi(server);
registerAddContentItemMapi(server);
registerUpdateContentItemMapi(server);
registerDeleteContentItemMapi(server);
registerUpsertLanguageVariantMapi(server);
registerCreateVariantVersionMapi(server);
registerDeleteLanguageVariantMapi(server);
registerListWorkflowsMapi(server);
registerChangeVariantWorkflowStepMapi(server);
registerFilterVariantsMapi(server);
registerSearchVariantsMapi(server);
registerPublishVariantMapi(server);
registerUnpublishVariantMapi(server);

return { server };
};
22 changes: 7 additions & 15 deletions src/telemetry/applicationInsights.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,6 @@ import appInsights from "applicationinsights";
import { AxiosError } from "axios";
import { sanitizeTelemetry, sanitizeUrl } from "./telemetrySanitizer.js";

export interface ApplicationInsightsConfig {
applicationInsightsConnectionString?: string;
projectLocation?: string;
}

let isInitialized = false;

function trackKontentApiError(
Expand Down Expand Up @@ -139,7 +134,7 @@ export function trackServerStartup(version: string): void {
}
}

function createTelemetryProcessor(config: ApplicationInsightsConfig) {
function createTelemetryProcessor() {
return (envelope: any) => {
sanitizeTelemetry(envelope);

Expand All @@ -148,22 +143,21 @@ function createTelemetryProcessor(config: ApplicationInsightsConfig) {
envelope.data.baseData.properties || {};
envelope.data.baseData.properties["component.name"] = "mcp-server";
envelope.data.baseData.properties["component.location"] =
config.projectLocation || "unknown";
process.env.projectLocation || "unknown";
}
return true;
};
}

export function initializeApplicationInsights(
config: ApplicationInsightsConfig | null,
): void {
export function initializeApplicationInsights(): void {
try {
if (!config?.applicationInsightsConnectionString) {
const connectionString = process.env.appInsightsConnectionString;
if (!connectionString) {
return;
}

appInsights
.setup(config.applicationInsightsConnectionString)
.setup(connectionString)
.setAutoCollectExceptions(true)
.setAutoCollectRequests(true)
.setAutoCollectConsole(false)
Expand All @@ -177,9 +171,7 @@ export function initializeApplicationInsights(

isInitialized = true;

appInsights.defaultClient.addTelemetryProcessor(
createTelemetryProcessor(config),
);
appInsights.defaultClient.addTelemetryProcessor(createTelemetryProcessor());
} catch (error) {
console.log("Failed to initialize Application Insights:", error);
}
Expand Down
8 changes: 2 additions & 6 deletions src/tools/add-content-item-mapi.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import { createMapiClient } from "../clients/kontentClients.js";
import type { AppConfiguration } from "../config/appConfiguration.js";
import { handleMcpToolError } from "../utils/errorHandler.js";
import { createMcpToolSuccessResponse } from "../utils/responseHelper.js";

export const registerTool = (
server: McpServer,
config: AppConfiguration | null,
): void => {
export const registerTool = (server: McpServer): void => {
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.",
Expand Down Expand Up @@ -54,7 +50,7 @@ export const registerTool = (
{ name, type, codename, external_id, collection },
{ authInfo: { token, clientId } = {} },
) => {
const client = createMapiClient(clientId, token, config);
const client = createMapiClient(clientId, token);

try {
const response = await client
Expand Down
Loading