#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
CallToolResult,
} from "@modelcontextprotocol/sdk/types.js";
import {
discoverTranslationsFile,
translationsFilePath,
findTranslations,
refreshTranslations,
} from "./translation-discovery.js";
import {
FindTranslationArgsSchema,
RefreshTranslationsArgsSchema,
} from "./types.js";
// Create server instance
const server = new Server(
{
name: "translations-mcp",
version: "0.1.0",
},
{
capabilities: {
tools: {},
},
}
);
// List available tools
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: "find_translation",
description:
"Find translation keys and values in the discovered translation file",
inputSchema: {
type: "object",
properties: {
query: {
type: "string",
description: "Search query to find in translation keys or values",
},
exact: {
type: "boolean",
description:
"Whether to search for exact matches only (default: false)",
default: false,
},
},
required: ["query"],
},
},
{
name: "refresh_translations",
description:
"Manually refresh and re-index translation files to pick up any changes",
inputSchema: {
type: "object",
properties: {},
required: [],
},
},
],
};
});
// Handle tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
switch (name) {
case "find_translation":
try {
const findArgs = FindTranslationArgsSchema.parse(args || {});
if (!translationsFilePath) {
return {
content: [
{
type: "text",
text: '❌ No translation file found. The server searches for translation.json or translations.json files in folders named "en" within the current working directory.',
},
],
} as CallToolResult;
}
const results = findTranslations(findArgs.query, findArgs.exact);
if (results.length === 0) {
return {
content: [
{
type: "text",
text: `🔍 No translations found for query: "${findArgs.query}"${
findArgs.exact ? " (exact match)" : ""
}`,
},
],
} as CallToolResult;
}
return {
content: [
{
type: "text",
text: JSON.stringify(
results.map((entry) => ({
key: entry.path,
value: entry.value,
})),
null,
2
),
},
],
} as CallToolResult;
} catch (error) {
return {
content: [
{
type: "text",
text: `❌ Error with find_translation tool: ${
error instanceof Error ? error.message : String(error)
}`,
},
],
} as CallToolResult;
}
case "refresh_translations":
try {
RefreshTranslationsArgsSchema.parse(args || {});
const result = await refreshTranslations();
return {
content: [
{
type: "text",
text: JSON.stringify(
{
success: result.success,
message: result.message,
entriesCount: result.entriesCount,
timestamp: new Date().toISOString(),
},
null,
2
),
},
],
} as CallToolResult;
} catch (error) {
return {
content: [
{
type: "text",
text: `❌ Error with refresh_translations tool: ${
error instanceof Error ? error.message : String(error)
}`,
},
],
} as CallToolResult;
}
default:
throw new Error(`Unknown tool: ${name}`);
}
});
async function main() {
// Get translation file path from environment variable or command line argument
const configuredPath = process.env.TRANSLATION_FILE_PATH || process.argv[2];
if (configuredPath) {
console.error(
`🔧 Using configured translation file path: ${configuredPath}`
);
} else {
console.error(`🔍 No translation file path configured, will auto-discover`);
}
// Discover translations file at startup
await discoverTranslationsFile(configuredPath);
const transport = new StdioServerTransport();
await server.connect(transport);
// This is important for MCP servers
console.error("Translation MCP server running on stdio");
}
main().catch((error) => {
console.error("Failed to run server:", error);
process.exit(1);
});
process.on("SIGINT", () => {
process.exit(0);
});
process.on("SIGTERM", () => {
process.exit(0);
});