import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { fetchFigmaFile } from "./figma-api.js";
import { extractTextNodes, toNestedJSON } from "./extract-figma-text.js";
const FIGMA_TOKEN = process.env.FIGMA_TOKEN;
// Create server instance
const server = new McpServer({
name: "figma-i18n-mcp",
version: "1.1.0",
});
// Tool: Extract Figma text for i18n
server.tool(
"extract_figma_i18n",
"Extracts all visible text from a Figma file, grouped by frame/component, for i18n translation.",
{
figmaUrl: z.string().describe("Figma file link or frame URL"),
},
async ({ figmaUrl }) => {
try {
const url = new URL(figmaUrl);
const match = figmaUrl.match(/(?:file|design)\/([a-zA-Z0-9]+)(?:\/.*)?/);
if (!match) {
return {
content: [
{
type: "text",
text: "Invalid Figma URL. Please provide a valid Figma file URL.",
},
],
};
}
const node = url.searchParams.get("node-id");
const fileKey = match[1];
const children = await fetchFigmaFile(fileKey, node, process.env.FIGMA_TOKEN);
// Extract text from all children (fetchFigmaFile returns an array)
let texts = [];
if (Array.isArray(children)) {
for (const child of children) {
texts = texts.concat(extractTextNodes(child));
}
} else {
texts = extractTextNodes(children);
}
const json = toNestedJSON(texts);
return {
content: [
{
type: "text",
text: JSON.stringify(json, null, 2),
},
],
};
} catch (error) {
console.error(`Error in extract_figma_i18n tool: ${error}`);
return {
content: [
{
type: "text",
text: `Error extracting Figma text: ${error instanceof Error ? error.message : "Unknown error"}`,
},
],
};
}
}
);
// Start the server
async function main() {
try {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Figma i18n MCP Server running on stdio");
} catch (error) {
console.error("Error starting server:", error);
process.exit(1);
}
}
main().catch((error) => {
console.error("Fatal error in main():", error);
process.exit(1);
});