Figma MCP Server

function getFigmaApiKey() { const apiKey = process.env.FIGMA_API_KEY; if (!apiKey) { throw new Error("FIGMA_API_KEY is not set"); } return apiKey; } export function parseKeyFromUrl(url: string) { // Extract key from URLs like: // https://www.figma.com/board/vJzJ1oVCzowAKAayQJx6Ug/... // https://www.figma.com/design/8SvxepW26v4d0AyyTAw23c/... // https://www.figma.com/file/8SvxepW26v4d0AyyTAw23c/... const matches = url.match(/figma\.com\/(board|design|file)\/([^/?]+)/); if (matches) { return matches[2]; // Return the second capture group which contains the key } throw new Error("Could not parse Figma key from URL"); } type FigNode = { id: string; name: string; type: string; children?: FigNode[]; }; type FigFile = { name: string; version: string; document: FigNode; thumbnailUrl: string; thumbnailB64: string; }; export function getCanvasIds(figFileJson: FigNode) { const canvasIds: string[] = []; const queue: FigNode[] = [figFileJson]; while (queue.length > 0) { const node = queue.shift()!; if (node.type === "CANVAS") { canvasIds.push(node.id); continue; // Skip children of canvases } if (node.children) { queue.push(...node.children); } } return canvasIds; } export async function downloadFigmaFile(key: string): Promise<FigFile> { const response = await fetch(`https://api.figma.com/v1/files/${key}`, { headers: { "X-FIGMA-TOKEN": getFigmaApiKey(), }, }); const data = await response.json(); return { ...data, thumbnailB64: await imageUrlToBase64(data.thumbnailUrl), }; } export async function getThumbnails(key: string, ids: string[]): Promise<{ [id: string]: string }> { const thumbnails = await fetch( `https://api.figma.com/v1/images/${key}?ids=${ids.join(",")}&format=png&page_size=1`, { headers: { "X-FIGMA-TOKEN": getFigmaApiKey(), }, } ); const data = (await thumbnails.json()) as { images: { [id: string]: string }; err?: string }; if (data.err) { throw new Error(`Error getting thumbnails: ${data.err}`); } return data.images; } export async function getThumbnailsOfCanvases( key: string, document: FigNode ): Promise<{ id: string; url: string; b64: string }[]> { const canvasIds = getCanvasIds(document); const thumbnails = await getThumbnails(key, canvasIds); const results = []; for (const [id, url] of Object.entries(thumbnails)) { results.push({ id, url, b64: await imageUrlToBase64(url), }); } return results; } export async function readComments(fileKey: string) { const response = await fetch(`https://api.figma.com/v1/files/${fileKey}/comments`, { headers: { "X-FIGMA-TOKEN": getFigmaApiKey(), }, }); return await response.json(); } export async function postComment( fileKey: string, message: string, x: number, y: number, nodeId?: string ) { const response = await fetch(`https://api.figma.com/v1/files/${fileKey}/comments`, { method: "POST", headers: { "X-FIGMA-TOKEN": getFigmaApiKey(), "Content-Type": "application/json", }, body: JSON.stringify({ message, client_meta: { node_offset: { x, y }, node_id: nodeId }, }), }); return await response.json(); } export async function replyToComment(fileKey: string, commentId: string, message: string) { const response = await fetch(`https://api.figma.com/v1/files/${fileKey}/comments`, { method: "POST", headers: { "X-FIGMA-TOKEN": getFigmaApiKey(), "Content-Type": "application/json", }, body: JSON.stringify({ message, comment_id: commentId, }), }); return await response.json(); } async function imageUrlToBase64(url: string) { const response = await fetch(url); const arrayBuffer = await response.arrayBuffer(); return Buffer.from(arrayBuffer).toString("base64"); }