Skip to main content
Glama

Cursor Talk to Figma MCP

by paragdesai1

set_text_content

Update the text content of a text node in Figma designs programmatically using the Cursor AI and Figma integration.

Instructions

Set the text content of an existing text node in Figma

Input Schema

NameRequiredDescriptionDefault

No arguments

Input Schema (JSON Schema)

{ "$schema": "http://json-schema.org/draft-07/schema#", "additionalProperties": false, "properties": {}, "type": "object" }

Implementation Reference

  • Main handler function for 'set_text_content' command. Validates parameters, retrieves the text node, loads its font, calls setCharacters helper to update text, and returns updated node info.
    async function setTextContent(params) { const { nodeId, text } = params || {}; if (!nodeId) { throw new Error("Missing nodeId parameter"); } if (text === undefined) { throw new Error("Missing text parameter"); } const node = await figma.getNodeByIdAsync(nodeId); if (!node) { throw new Error(`Node not found with ID: ${nodeId}`); } if (node.type !== "TEXT") { throw new Error(`Node is not a text node: ${nodeId}`); } try { await figma.loadFontAsync(node.fontName); await setCharacters(node, text); return { id: node.id, name: node.name, characters: node.characters, fontName: node.fontName, }; } catch (error) { throw new Error(`Error setting text content: ${error.message}`); } }
  • Core helper function that intelligently handles font loading (including mixed fonts) and sets the characters property on a Figma text node. Used by setTextContent.
    export const setCharacters = async (node, characters, options) => { const fallbackFont = options?.fallbackFont || { family: "Roboto", style: "Regular", }; try { if (node.fontName === figma.mixed) { if (options?.smartStrategy === "prevail") { const fontHashTree = {}; for (let i = 1; i < node.characters.length; i++) { const charFont = node.getRangeFontName(i - 1, i); const key = `${charFont.family}::${charFont.style}`; fontHashTree[key] = fontHashTree[key] ? fontHashTree[key] + 1 : 1; } const prevailedTreeItem = Object.entries(fontHashTree).sort( (a, b) => b[1] - a[1] )[0]; const [family, style] = prevailedTreeItem[0].split("::"); const prevailedFont = { family, style, }; await figma.loadFontAsync(prevailedFont); node.fontName = prevailedFont; } else if (options?.smartStrategy === "strict") { return setCharactersWithStrictMatchFont(node, characters, fallbackFont); } else if (options?.smartStrategy === "experimental") { return setCharactersWithSmartMatchFont(node, characters, fallbackFont); } else { const firstCharFont = node.getRangeFontName(0, 1); await figma.loadFontAsync(firstCharFont); node.fontName = firstCharFont; } } else { await figma.loadFontAsync({ family: node.fontName.family, style: node.fontName.style, }); } } catch (err) { console.warn( `Failed to load "${node.fontName["family"]} ${node.fontName["style"]}" font and replaced with fallback "${fallbackFont.family} ${fallbackFont.style}"`, err ); await figma.loadFontAsync(fallbackFont); node.fontName = fallbackFont; } try { node.characters = characters; return true; } catch (err) { console.warn(`Failed to set characters. Skipped.`, err); return false; } };
  • MCP server.tool registration for 'set_text_content'. Defines input schema (nodeId, text) and forwards the command to the Figma plugin via WebSocket.
    server.tool( "set_text_content", "Set the text content of an existing text node in Figma", { nodeId: z.string().describe("The ID of the text node to modify"), text: z.string().describe("New text content"), }, async ({ nodeId, text }) => { try { const result = await sendCommandToFigma("set_text_content", { nodeId, text, }); const typedResult = result as { name: string }; return { content: [ { type: "text", text: `Updated text content of node "${typedResult.name}" to "${text}"`, }, ], }; } catch (error) { return { content: [ { type: "text", text: `Error setting text content: ${error instanceof Error ? error.message : String(error) }`, }, ], }; } } );
  • Dispatch case in handleCommand switch statement that routes 'set_text_content' command to the setTextContent handler function.
    case "set_text_content": return await setTextContent(params);
  • Supporting helper functions for advanced font handling in mixed-font scenarios, used by setCharacters depending on options.smartStrategy.
    const setCharactersWithStrictMatchFont = async ( node, characters, fallbackFont ) => { const fontHashTree = {}; for (let i = 1; i < node.characters.length; i++) { const startIdx = i - 1; const startCharFont = node.getRangeFontName(startIdx, i); const startCharFontVal = `${startCharFont.family}::${startCharFont.style}`; while (i < node.characters.length) { i++; const charFont = node.getRangeFontName(i - 1, i); if (startCharFontVal !== `${charFont.family}::${charFont.style}`) { break; } } fontHashTree[`${startIdx}_${i}`] = startCharFontVal; } await figma.loadFontAsync(fallbackFont); node.fontName = fallbackFont; node.characters = characters; console.log(fontHashTree); await Promise.all( Object.keys(fontHashTree).map(async (range) => { console.log(range, fontHashTree[range]); const [start, end] = range.split("_"); const [family, style] = fontHashTree[range].split("::"); const matchedFont = { family, style, }; await figma.loadFontAsync(matchedFont); return node.setRangeFontName(Number(start), Number(end), matchedFont); }) ); return true; }; const getDelimiterPos = (str, delimiter, startIdx = 0, endIdx = str.length) => { const indices = []; let temp = startIdx; for (let i = startIdx; i < endIdx; i++) { if ( str[i] === delimiter && i + startIdx !== endIdx && temp !== i + startIdx ) { indices.push([temp, i + startIdx]); temp = i + startIdx + 1; } } temp !== endIdx && indices.push([temp, endIdx]); return indices.filter(Boolean); }; const buildLinearOrder = (node) => { const fontTree = []; const newLinesPos = getDelimiterPos(node.characters, "\n"); newLinesPos.forEach(([newLinesRangeStart, newLinesRangeEnd], n) => { const newLinesRangeFont = node.getRangeFontName( newLinesRangeStart, newLinesRangeEnd ); if (newLinesRangeFont === figma.mixed) { const spacesPos = getDelimiterPos( node.characters, " ", newLinesRangeStart, newLinesRangeEnd ); spacesPos.forEach(([spacesRangeStart, spacesRangeEnd], s) => { const spacesRangeFont = node.getRangeFontName( spacesRangeStart, spacesRangeEnd ); if (spacesRangeFont === figma.mixed) { const spacesRangeFont = node.getRangeFontName( spacesRangeStart, spacesRangeStart[0] ); fontTree.push({ start: spacesRangeStart, delimiter: " ", family: spacesRangeFont.family, style: spacesRangeFont.style, }); } else { fontTree.push({ start: spacesRangeStart, delimiter: " ", family: spacesRangeFont.family, style: spacesRangeFont.style, }); } }); } else { fontTree.push({ start: newLinesRangeStart, delimiter: "\n", family: newLinesRangeFont.family, style: newLinesRangeFont.style, }); } }); return fontTree .sort((a, b) => +a.start - +b.start) .map(({ family, style, delimiter }) => ({ family, style, delimiter })); }; const setCharactersWithSmartMatchFont = async ( node, characters, fallbackFont ) => { const rangeTree = buildLinearOrder(node); const fontsToLoad = uniqBy( rangeTree, ({ family, style }) => `${family}::${style}` ).map(({ family, style }) => ({ family, style, })); await Promise.all([...fontsToLoad, fallbackFont].map(figma.loadFontAsync)); node.fontName = fallbackFont; node.characters = characters; let prevPos = 0; rangeTree.forEach(({ family, style, delimiter }) => { if (prevPos < node.characters.length) { const delimeterPos = node.characters.indexOf(delimiter, prevPos); const endPos = delimeterPos > prevPos ? delimeterPos : node.characters.length; const matchedFont = { family, style, }; node.setRangeFontName(prevPos, endPos, matchedFont); prevPos = endPos + 1; } }); return true; };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/paragdesai1/parag-Figma-MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server