lokalise-mcp
Version:
The Lokalise MCP Server brings Lokalise's localization power to Claude and AI assistants—manage projects, keys, and translations by chat.
157 lines (139 loc) • 4.86 kB
text/typescript
import type { SupportedPlatforms } from "@lokalise/node-api";
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
import type {
DomainMeta,
DomainResource,
} from "../../shared/types/domain.types.js";
import { formatErrorForMcpResource } from "../../shared/utils/error.util.js";
import { Logger } from "../../shared/utils/logger.util.js";
import controller from "./keys.controller.js";
const logger = Logger.forContext("keys.resource.ts");
/**
* Register all MCP resources for the keys domain
* @param server The MCP server instance to register resources with
*/
function registerResources(server: McpServer): void {
const registerLogger = logger.forMethod("registerResources");
registerLogger.debug("Registering keys domain resources...");
// Register the project keys resource
server.resource(
"lokalise-project-keys",
new ResourceTemplate("lokalise://keys/{projectId}", { list: undefined }),
async (uri: URL) => {
const methodLogger = logger.forMethod("projectKeysResource");
try {
// Extract the project ID from the request path
// Format: lokalise://keys/{projectId}?includeTranslations=true&page=1&limit=100&cursor=abc123
methodLogger.debug("Project keys resource called", {
uri: uri.toString(),
});
// Get project ID from the path (after 'keys/')
const pathParts = uri.pathname.split("/").filter(Boolean);
const projectId = pathParts[1]; // Second part is the project ID after 'keys'
if (!projectId) {
throw new Error("Project ID is required in the URI path");
}
const urlParams = new URLSearchParams(uri.search);
const includeTranslations =
urlParams.get("includeTranslations") === "true";
const page = urlParams.get("page")
? Number.parseInt(urlParams.get("page") ?? "1", 10)
: undefined;
const limit = urlParams.get("limit")
? Number.parseInt(urlParams.get("limit") ?? "100", 10)
: undefined;
const filterKeys = urlParams.get("filterKeys")?.split(",") || undefined;
const filterPlatforms =
(urlParams
.get("filterPlatforms")
?.split(",") as SupportedPlatforms[]) || undefined;
// Call the controller to get the project keys
const result = await controller.listKeys({
projectId,
includeTranslations,
page,
limit,
filterKeys,
filterPlatforms,
});
// Return the content as a text resource
return {
contents: [
{
uri: uri.toString(),
text: result.content,
mimeType: "text/markdown",
description: `Lokalise Project Keys: ${projectId}${includeTranslations ? " (with translations)" : ""}${filterPlatforms ? ` (platforms: ${filterPlatforms.join(",")})` : ""}`,
},
],
};
} catch (error) {
methodLogger.error("Project keys resource error", error);
return formatErrorForMcpResource(error, uri.toString());
}
},
);
// Register the single key resource
server.resource(
"lokalise-key-details",
new ResourceTemplate("lokalise://keys/{projectId}/{keyId}", {
list: undefined,
}),
async (uri: URL) => {
const methodLogger = logger.forMethod("keyDetailsResource");
try {
// Extract the project ID and key ID from the request path
// Format: lokalise://keys/{projectId}/{keyId}?includeTranslations=true
methodLogger.debug("Key details resource called", {
uri: uri.toString(),
});
// Get project ID and key ID from the path (after 'keys/')
const pathParts = uri.pathname.split("/").filter(Boolean);
const projectId = pathParts[1]; // Second part is the project ID after 'keys'
const keyId = pathParts[2]; // Third part is the key ID
if (!projectId || !keyId) {
throw new Error(
"Both project ID and key ID are required in the URI path",
);
}
// Call the controller to get the key details
const result = await controller.getKey({
projectId,
keyId: Number.parseInt(keyId, 10),
});
// Return the content as a text resource
return {
contents: [
{
uri: uri.toString(),
text: result.content,
mimeType: "text/markdown",
description: `Lokalise Key Details: ${keyId} (Project: ${projectId})`,
},
],
};
} catch (error) {
methodLogger.error("Key details resource error", error);
return formatErrorForMcpResource(error, uri.toString());
}
},
);
registerLogger.debug("Keys domain resources registered successfully");
}
/**
* Get metadata about the keys domain resources
*/
function getMeta(): DomainMeta {
return {
name: "keys",
description: "Lokalise keys domain resources",
version: "1.0.0",
resourcesCount: 2,
};
}
const keysResource: DomainResource = {
registerResources,
getMeta,
};
export default keysResource;