From b87d3d41fbc084d9057d99ff4bb45967464470a2 Mon Sep 17 00:00:00 2001 From: JooHyung Park Date: Wed, 13 Aug 2025 23:24:39 +0800 Subject: [PATCH 1/8] feat: support counterAxisSpacing in set_item_spacing tool and related server.ts changes --- src/talk_to_figma_mcp/server.ts | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/talk_to_figma_mcp/server.ts b/src/talk_to_figma_mcp/server.ts index 8ed625e..f7b5620 100644 --- a/src/talk_to_figma_mcp/server.ts +++ b/src/talk_to_figma_mcp/server.ts @@ -2298,21 +2298,27 @@ server.tool( "Set distance between children in an auto-layout frame", { nodeId: z.string().describe("The ID of the frame to modify"), - itemSpacing: z.number().describe("Distance between children. Note: This value will be ignored if primaryAxisAlignItems is set to SPACE_BETWEEN.") + itemSpacing: z.number().optional().describe("Distance between children. Note: This value will be ignored if primaryAxisAlignItems is set to SPACE_BETWEEN."), + counterAxisSpacing: z.number().optional().describe("Distance between wrapped rows/columns. Only works when layoutWrap is set to WRAP.") }, - async ({ nodeId, itemSpacing }: any) => { + async ({ nodeId, itemSpacing, counterAxisSpacing}: any) => { try { - const result = await sendCommandToFigma("set_item_spacing", { - nodeId, - itemSpacing - }); - const typedResult = result as { name: string }; + const params: any = { nodeId }; + if (itemSpacing !== undefined) params.itemSpacing = itemSpacing; + if (counterAxisSpacing !== undefined) params.counterAxisSpacing = counterAxisSpacing; + + const result = await sendCommandToFigma("set_item_spacing", params); + const typedResult = result as { name: string, itemSpacing?: number, counterAxisSpacing?: number }; + + let message = `Updated spacing for frame "${typedResult.name}":`; + if (itemSpacing !== undefined) message += ` itemSpacing=${itemSpacing}`; + if (counterAxisSpacing !== undefined) message += ` counterAxisSpacing=${counterAxisSpacing}`; return { content: [ { type: "text", - text: `Set item spacing to ${itemSpacing} for frame "${typedResult.name}"`, + text: message, }, ], }; @@ -2321,7 +2327,7 @@ server.tool( content: [ { type: "text", - text: `Error setting item spacing: ${error instanceof Error ? error.message : String(error)}`, + text: `Error setting spacing: ${error instanceof Error ? error.message : String(error)}`, }, ], }; From 131d85eb0db402237e85e5c51d99b7c9043a2978 Mon Sep 17 00:00:00 2001 From: JooHyung Park Date: Wed, 13 Aug 2025 23:27:10 +0800 Subject: [PATCH 2/8] feat: set_item_spacing supports counterAxisSpacing; no other code.js changes --- src/cursor_mcp_plugin/code.js | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/src/cursor_mcp_plugin/code.js b/src/cursor_mcp_plugin/code.js index b5fb794..cdc8a04 100644 --- a/src/cursor_mcp_plugin/code.js +++ b/src/cursor_mcp_plugin/code.js @@ -3480,7 +3480,12 @@ async function setLayoutSizing(params) { } async function setItemSpacing(params) { - const { nodeId, itemSpacing } = params || {}; + const { nodeId, itemSpacing, counterAxisSpacing } = params || {}; + + // Validate that at least one spacing parameter is provided + if (itemSpacing === undefined && counterAxisSpacing === undefined) { + throw new Error("At least one of itemSpacing or counterAxisSpacing must be provided"); + } // Get the target node const node = await figma.getNodeByIdAsync(nodeId); @@ -3505,7 +3510,7 @@ async function setItemSpacing(params) { ); } - // Set item spacing + // Set item spacing if provided if (itemSpacing !== undefined) { if (typeof itemSpacing !== "number") { throw new Error("Item spacing must be a number"); @@ -3513,11 +3518,27 @@ async function setItemSpacing(params) { node.itemSpacing = itemSpacing; } + // Set counter axis spacing if provided + if (counterAxisSpacing !== undefined) { + if (typeof counterAxisSpacing !== "number") { + throw new Error("Counter axis spacing must be a number"); + } + // counterAxisSpacing only applies when layoutWrap is WRAP + if (node.layoutWrap !== "WRAP") { + throw new Error( + "Counter axis spacing can only be set on frames with layoutWrap set to WRAP" + ); + } + node.counterAxisSpacing = counterAxisSpacing; + } + return { id: node.id, name: node.name, - itemSpacing: node.itemSpacing, + itemSpacing: node.itemSpacing || undefined, + counterAxisSpacing: node.counterAxisSpacing || undefined, layoutMode: node.layoutMode, + layoutWrap: node.layoutWrap, }; } From aeb5bb652b6584d01cf27da9e4dd580670c6853d Mon Sep 17 00:00:00 2001 From: JooHyung Park Date: Wed, 13 Aug 2025 23:44:02 +0800 Subject: [PATCH 3/8] bugfix: getting annotations --- src/cursor_mcp_plugin/code.js | 18 +++++++++++++++++- src/talk_to_figma_mcp/server.ts | 2 +- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/cursor_mcp_plugin/code.js b/src/cursor_mcp_plugin/code.js index cdc8a04..15a1afe 100644 --- a/src/cursor_mcp_plugin/code.js +++ b/src/cursor_mcp_plugin/code.js @@ -2386,10 +2386,26 @@ async function getAnnotations(params) { throw new Error(`Node type ${node.type} does not support annotations`); } + // Collect annotations from this node and all its descendants + const mergedAnnotations = []; + const collect = async (n) => { + if ("annotations" in n && n.annotations && n.annotations.length > 0) { + for (const a of n.annotations) { + mergedAnnotations.push({ nodeId: n.id, annotation: a }); + } + } + if ("children" in n) { + for (const child of n.children) { + await collect(child); + } + } + }; + await collect(node); + const result = { nodeId: node.id, name: node.name, - annotations: node.annotations || [], + annotations: mergedAnnotations, }; if (includeCategories) { diff --git a/src/talk_to_figma_mcp/server.ts b/src/talk_to_figma_mcp/server.ts index f7b5620..d55d810 100644 --- a/src/talk_to_figma_mcp/server.ts +++ b/src/talk_to_figma_mcp/server.ts @@ -997,7 +997,7 @@ server.tool( "get_annotations", "Get all annotations in the current document or specific node", { - nodeId: z.string().optional().describe("Optional node ID to get annotations for specific node"), + nodeId: z.string().describe("node ID to get annotations for specific node"), includeCategories: z.boolean().optional().default(true).describe("Whether to include category information") }, async ({ nodeId, includeCategories }: any) => { From f4c2b4cb8e1bb578be8f74db35edaa65e20a5d63 Mon Sep 17 00:00:00 2001 From: JooHyung Park Date: Tue, 2 Sep 2025 00:06:48 +0900 Subject: [PATCH 4/8] Add focus and selection tools --- src/cursor_mcp_plugin/code.js | 76 +++++++++++++++++++++++++++++++++ src/talk_to_figma_mcp/server.ts | 74 +++++++++++++++++++++++++++++++- 2 files changed, 149 insertions(+), 1 deletion(-) diff --git a/src/cursor_mcp_plugin/code.js b/src/cursor_mcp_plugin/code.js index 15a1afe..51b0b57 100644 --- a/src/cursor_mcp_plugin/code.js +++ b/src/cursor_mcp_plugin/code.js @@ -229,6 +229,10 @@ async function handleCommand(command, params) { return await setDefaultConnector(params); case "create_connections": return await createConnections(params); + case "set_focus": + return await setFocus(params); + case "set_selections": + return await setSelections(params); default: throw new Error(`Unknown command: ${command}`); } @@ -3950,3 +3954,75 @@ async function createConnections(params) { connections: results }; } + +// Set focus on a specific node +async function setFocus(params) { + if (!params || !params.nodeId) { + throw new Error("Missing nodeId parameter"); + } + + const node = await figma.getNodeByIdAsync(params.nodeId); + if (!node) { + throw new Error(`Node with ID ${params.nodeId} not found`); + } + + // Set selection to the node + figma.currentPage.selection = [node]; + + // Scroll and zoom to show the node in viewport + figma.viewport.scrollAndZoomIntoView([node]); + + return { + success: true, + name: node.name, + id: node.id, + message: `Focused on node "${node.name}"` + }; +} + +// Set selection to multiple nodes +async function setSelections(params) { + if (!params || !params.nodeIds || !Array.isArray(params.nodeIds)) { + throw new Error("Missing or invalid nodeIds parameter"); + } + + if (params.nodeIds.length === 0) { + throw new Error("nodeIds array cannot be empty"); + } + + // Get all valid nodes + const nodes = []; + const notFoundIds = []; + + for (const nodeId of params.nodeIds) { + const node = await figma.getNodeByIdAsync(nodeId); + if (node) { + nodes.push(node); + } else { + notFoundIds.push(nodeId); + } + } + + if (nodes.length === 0) { + throw new Error(`No valid nodes found for the provided IDs: ${params.nodeIds.join(', ')}`); + } + + // Set selection to the nodes + figma.currentPage.selection = nodes; + + // Scroll and zoom to show all nodes in viewport + figma.viewport.scrollAndZoomIntoView(nodes); + + const selectedNodes = nodes.map(node => ({ + name: node.name, + id: node.id + })); + + return { + success: true, + count: nodes.length, + selectedNodes: selectedNodes, + notFoundIds: notFoundIds, + message: `Selected ${nodes.length} nodes${notFoundIds.length > 0 ? ` (${notFoundIds.length} not found)` : ''}` + }; +} diff --git a/src/talk_to_figma_mcp/server.ts b/src/talk_to_figma_mcp/server.ts index d55d810..7b16785 100644 --- a/src/talk_to_figma_mcp/server.ts +++ b/src/talk_to_figma_mcp/server.ts @@ -2458,6 +2458,70 @@ server.tool( } ); +// Set Focus Tool +server.tool( + "set_focus", + "Set focus on a specific node in Figma by selecting it and scrolling viewport to it", + { + nodeId: z.string().describe("The ID of the node to focus on"), + }, + async ({ nodeId }: any) => { + try { + const result = await sendCommandToFigma("set_focus", { nodeId }); + const typedResult = result as { name: string; id: string }; + return { + content: [ + { + type: "text", + text: `Focused on node "${typedResult.name}" (ID: ${typedResult.id})`, + }, + ], + }; + } catch (error) { + return { + content: [ + { + type: "text", + text: `Error setting focus: ${error instanceof Error ? error.message : String(error)}`, + }, + ], + }; + } + } +); + +// Set Selections Tool +server.tool( + "set_selections", + "Set selection to multiple nodes in Figma and scroll viewport to show them", + { + nodeIds: z.array(z.string()).describe("Array of node IDs to select"), + }, + async ({ nodeIds }: any) => { + try { + const result = await sendCommandToFigma("set_selections", { nodeIds }); + const typedResult = result as { selectedNodes: Array<{ name: string; id: string }>; count: number }; + return { + content: [ + { + type: "text", + text: `Selected ${typedResult.count} nodes: ${typedResult.selectedNodes.map(node => `"${node.name}" (${node.id})`).join(', ')}`, + }, + ], + }; + } catch (error) { + return { + content: [ + { + type: "text", + text: `Error setting selections: ${error instanceof Error ? error.message : String(error)}`, + }, + ], + }; + } + } +); + // Strategy for converting Figma prototype reactions to connector lines server.prompt( "reaction_to_connector_strategy", @@ -2582,7 +2646,9 @@ type FigmaCommand = | "set_item_spacing" | "get_reactions" | "set_default_connector" - | "create_connections"; + | "create_connections" + | "set_focus" + | "set_selections"; type CommandParams = { get_document_info: Record; @@ -2725,6 +2791,12 @@ type CommandParams = { text?: string; }>; }; + set_focus: { + nodeId: string; + }; + set_selections: { + nodeIds: string[]; + }; }; From b04b2d506c881c177e7008b73376f55a136d37e2 Mon Sep 17 00:00:00 2001 From: JooHyung Park Date: Tue, 2 Sep 2025 00:21:05 +0900 Subject: [PATCH 5/8] Update readme.md --- readme.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/readme.md b/readme.md index 3ad7648..fe8502f 100644 --- a/readme.md +++ b/readme.md @@ -133,6 +133,8 @@ The MCP server provides the following tools for interacting with Figma: - `read_my_design` - Get detailed node information about the current selection without parameters - `get_node_info` - Get detailed information about a specific node - `get_nodes_info` - Get detailed information about multiple nodes by providing an array of node IDs +- `set_focus` - Set focus on a specific node by selecting it and scrolling viewport to it +- `set_selections` - Set selection to multiple nodes and scroll viewport to show them ### Annotations From 9aced2547552eef6e9939690a2b2e9f63bf34acf Mon Sep 17 00:00:00 2001 From: JooHyung Park Date: Tue, 2 Sep 2025 00:31:15 +0900 Subject: [PATCH 6/8] Disable FigJam Connector API functionality Fixes #106 - API has been officially blocked by Figma --- readme.md | 8 - src/cursor_mcp_plugin/code.js | 583 ++++++++++++++++---------------- src/talk_to_figma_mcp/server.ts | 380 +++++++++++---------- 3 files changed, 492 insertions(+), 479 deletions(-) diff --git a/readme.md b/readme.md index fe8502f..6cea53a 100644 --- a/readme.md +++ b/readme.md @@ -146,8 +146,6 @@ The MCP server provides the following tools for interacting with Figma: ### Prototyping & Connections - `get_reactions` - Get all prototype reactions from nodes with visual highlight animation -- `set_default_connector` - Set a copied FigJam connector as the default connector style for creating connections (must be set before creating connections) -- `create_connections` - Create FigJam connector lines between nodes, based on prototype flows or custom mapping ### Creating Elements @@ -208,7 +206,6 @@ The MCP server includes several helper prompts to guide you through complex desi - `text_replacement_strategy` - Systematic approach for replacing text in Figma designs - `annotation_conversion_strategy` - Strategy for converting manual annotations to Figma's native annotations - `swap_overrides_instances` - Strategy for transferring overrides between component instances in Figma -- `reaction_to_connector_strategy` - Strategy for converting Figma prototype reactions to connector lines using the output of 'get_reactions', and guiding the use 'create_connections' in sequence ## Development @@ -252,11 +249,6 @@ When working with the Figma MCP: - Create native annotations with `set_multiple_annotations` in batches - Verify all annotations are properly linked to their targets - Delete legacy annotation nodes after successful conversion -11. Visualize prototype noodles as FigJam connectors: - -- Use `get_reactions` to extract prototype flows, -- set a default connector with `set_default_connector`, -- and generate connector lines with `create_connections` for clear visual flow mapping. ## License diff --git a/src/cursor_mcp_plugin/code.js b/src/cursor_mcp_plugin/code.js index 51b0b57..8e7ca89 100644 --- a/src/cursor_mcp_plugin/code.js +++ b/src/cursor_mcp_plugin/code.js @@ -225,10 +225,11 @@ async function handleCommand(command, params) { throw new Error("Missing or invalid nodeIds parameter"); } return await getReactions(params.nodeIds); - case "set_default_connector": - return await setDefaultConnector(params); - case "create_connections": - return await createConnections(params); + // NOTE: Connector functionality disabled due to Figma API deprecation + // case "set_default_connector": + // return await setDefaultConnector(params); + // case "create_connections": + // return await createConnections(params); case "set_focus": return await setFocus(params); case "set_selections": @@ -3562,92 +3563,96 @@ async function setItemSpacing(params) { }; } -async function setDefaultConnector(params) { - const { connectorId } = params || {}; - - // If connectorId is provided, search and set by that ID (do not check existing storage) - if (connectorId) { - // Get node by specified ID - const node = await figma.getNodeByIdAsync(connectorId); - if (!node) { - throw new Error(`Connector node not found with ID: ${connectorId}`); - } - - // Check node type - if (node.type !== 'CONNECTOR') { - throw new Error(`Node is not a connector: ${connectorId}`); - } - - // Set the found connector as the default connector - await figma.clientStorage.setAsync('defaultConnectorId', connectorId); - - return { - success: true, - message: `Default connector set to: ${connectorId}`, - connectorId: connectorId - }; - } - // If connectorId is not provided, check existing storage - else { - // Check if there is an existing default connector in client storage - try { - const existingConnectorId = await figma.clientStorage.getAsync('defaultConnectorId'); - - // If there is an existing connector ID, check if the node is still valid - if (existingConnectorId) { - try { - const existingConnector = await figma.getNodeByIdAsync(existingConnectorId); - - // If the stored connector still exists and is of type CONNECTOR - if (existingConnector && existingConnector.type === 'CONNECTOR') { - return { - success: true, - message: `Default connector is already set to: ${existingConnectorId}`, - connectorId: existingConnectorId, - exists: true - }; - } - // The stored connector is no longer valid - find a new connector - else { - console.log(`Stored connector ID ${existingConnectorId} is no longer valid, finding a new connector...`); - } - } catch (error) { - console.log(`Error finding stored connector: ${error.message}. Will try to set a new one.`); - } - } - } catch (error) { - console.log(`Error checking for existing connector: ${error.message}`); - } - - // If there is no stored default connector or it is invalid, find one in the current page - try { - // Find CONNECTOR type nodes in the current page - const currentPageConnectors = figma.currentPage.findAllWithCriteria({ types: ['CONNECTOR'] }); - - if (currentPageConnectors && currentPageConnectors.length > 0) { - // Use the first connector found - const foundConnector = currentPageConnectors[0]; - const autoFoundId = foundConnector.id; - - // Set the found connector as the default connector - await figma.clientStorage.setAsync('defaultConnectorId', autoFoundId); - - return { - success: true, - message: `Automatically found and set default connector to: ${autoFoundId}`, - connectorId: autoFoundId, - autoSelected: true - }; - } else { - // If no connector is found in the current page, show a guide message - throw new Error('No connector found in the current page. Please create a connector in Figma first or specify a connector ID.'); - } - } catch (error) { - // Error occurred while running findAllWithCriteria - throw new Error(`Failed to find a connector: ${error.message}`); - } - } -} +// NOTE: Connector functionality has been disabled due to Figma API deprecation +// Figma has officially blocked the FigJam Connector API +// See: https://github.com/grab/cursor-talk-to-figma-mcp/issues/106 + +// async function setDefaultConnector(params) { +// const { connectorId } = params || {}; +// +// // If connectorId is provided, search and set by that ID (do not check existing storage) +// if (connectorId) { +// // Get node by specified ID +// const node = await figma.getNodeByIdAsync(connectorId); +// if (!node) { +// throw new Error(`Connector node not found with ID: ${connectorId}`); +// } +// +// // Check node type +// if (node.type !== 'CONNECTOR') { +// throw new Error(`Node is not a connector: ${connectorId}`); +// } +// +// // Set the found connector as the default connector +// await figma.clientStorage.setAsync('defaultConnectorId', connectorId); +// +// return { +// success: true, +// message: `Default connector set to: ${connectorId}`, +// connectorId: connectorId +// }; +// } +// // If connectorId is not provided, check existing storage +// else { +// // Check if there is an existing default connector in client storage +// try { +// const existingConnectorId = await figma.clientStorage.getAsync('defaultConnectorId'); +// +// // If there is an existing connector ID, check if the node is still valid +// if (existingConnectorId) { +// try { +// const existingConnector = await figma.getNodeByIdAsync(existingConnectorId); +// +// // If the stored connector still exists and is of type CONNECTOR +// if (existingConnector && existingConnector.type === 'CONNECTOR') { +// return { +// success: true, +// message: `Default connector is already set to: ${existingConnectorId}`, +// connectorId: existingConnectorId, +// exists: true +// }; +// } +// // The stored connector is no longer valid - find a new connector +// else { +// console.log(`Stored connector ID ${existingConnectorId} is no longer valid, finding a new connector...`); +// } +// } catch (error) { +// console.log(`Error finding stored connector: ${error.message}. Will try to set a new one.`); +// } +// } +// } catch (error) { +// console.log(`Error checking for existing connector: ${error.message}`); +// } +// +// // If there is no stored default connector or it is invalid, find one in the current page +// try { +// // Find CONNECTOR type nodes in the current page +// const currentPageConnectors = figma.currentPage.findAllWithCriteria({ types: ['CONNECTOR'] }); +// +// if (currentPageConnectors && currentPageConnectors.length > 0) { +// // Use the first connector found +// const foundConnector = currentPageConnectors[0]; +// const autoFoundId = foundConnector.id; +// +// // Set the found connector as the default connector +// await figma.clientStorage.setAsync('defaultConnectorId', autoFoundId); +// +// return { +// success: true, +// message: `Automatically found and set default connector to: ${autoFoundId}`, +// connectorId: autoFoundId, +// autoSelected: true +// }; +// } else { +// // If no connector is found in the current page, show a guide message +// throw new Error('No connector found in the current page. Please create a connector in Figma first or specify a connector ID.'); +// } +// } catch (error) { +// // Error occurred while running findAllWithCriteria +// throw new Error(`Failed to find a connector: ${error.message}`); +// } +// } +// } async function createCursorNode(targetNodeId) { const svgString = ` @@ -3756,204 +3761,208 @@ async function createCursorNode(targetNodeId) { } } -async function createConnections(params) { - if (!params || !params.connections || !Array.isArray(params.connections)) { - throw new Error('Missing or invalid connections parameter'); - } - - const { connections } = params; - - // Command ID for progress tracking - const commandId = generateCommandId(); - sendProgressUpdate( - commandId, - "create_connections", - "started", - 0, - connections.length, - 0, - `Starting to create ${connections.length} connections` - ); - - // Get default connector ID from client storage - const defaultConnectorId = await figma.clientStorage.getAsync('defaultConnectorId'); - if (!defaultConnectorId) { - throw new Error('No default connector set. Please try one of the following options to create connections:\n1. Create a connector in FigJam and copy/paste it to your current page, then run the "set_default_connector" command.\n2. Select an existing connector on the current page, then run the "set_default_connector" command.'); - } - - // Get the default connector - const defaultConnector = await figma.getNodeByIdAsync(defaultConnectorId); - if (!defaultConnector) { - throw new Error(`Default connector not found with ID: ${defaultConnectorId}`); - } - if (defaultConnector.type !== 'CONNECTOR') { - throw new Error(`Node is not a connector: ${defaultConnectorId}`); - } - - // Results array for connection creation - const results = []; - let processedCount = 0; - const totalCount = connections.length; - - // Preload fonts (used for text if provided) - let fontLoaded = false; - - for (let i = 0; i < connections.length; i++) { - try { - const { startNodeId: originalStartId, endNodeId: originalEndId, text } = connections[i]; - let startId = originalStartId; - let endId = originalEndId; - - // Check and potentially replace start node ID - if (startId.includes(';')) { - console.log(`Nested start node detected: ${startId}. Creating cursor node.`); - const cursorResult = await createCursorNode(startId); - if (!cursorResult || !cursorResult.id) { - throw new Error(`Failed to create cursor node for nested start node: ${startId}`); - } - startId = cursorResult.id; - } - - const startNode = await figma.getNodeByIdAsync(startId); - if (!startNode) throw new Error(`Start node not found with ID: ${startId}`); - - // Check and potentially replace end node ID - if (endId.includes(';')) { - console.log(`Nested end node detected: ${endId}. Creating cursor node.`); - const cursorResult = await createCursorNode(endId); - if (!cursorResult || !cursorResult.id) { - throw new Error(`Failed to create cursor node for nested end node: ${endId}`); - } - endId = cursorResult.id; - } - const endNode = await figma.getNodeByIdAsync(endId); - if (!endNode) throw new Error(`End node not found with ID: ${endId}`); +// NOTE: Connector functionality has been disabled due to Figma API deprecation +// Figma has officially blocked the FigJam Connector API +// See: https://github.com/grab/cursor-talk-to-figma-mcp/issues/106 - - // Clone the default connector - const clonedConnector = defaultConnector.clone(); - - // Update connector name using potentially replaced node names - clonedConnector.name = `TTF_Connector/${startNode.id}/${endNode.id}`; - - // Set start and end points using potentially replaced IDs - clonedConnector.connectorStart = { - endpointNodeId: startId, - magnet: 'AUTO' - }; - - clonedConnector.connectorEnd = { - endpointNodeId: endId, - magnet: 'AUTO' - }; - - // Add text (if provided) - if (text) { - try { - // Try to load the necessary fonts - try { - // First check if default connector has font and use the same - if (defaultConnector.text && defaultConnector.text.fontName) { - const fontName = defaultConnector.text.fontName; - await figma.loadFontAsync(fontName); - clonedConnector.text.fontName = fontName; - } else { - // Try default Inter font - await figma.loadFontAsync({ family: "Inter", style: "Regular" }); - } - } catch (fontError) { - // If first font load fails, try another font style - try { - await figma.loadFontAsync({ family: "Inter", style: "Medium" }); - } catch (mediumFontError) { - // If second font fails, try system font - try { - await figma.loadFontAsync({ family: "System", style: "Regular" }); - } catch (systemFontError) { - // If all font loading attempts fail, throw error - throw new Error(`Failed to load any font: ${fontError.message}`); - } - } - } - - // Set the text - clonedConnector.text.characters = text; - } catch (textError) { - console.error("Error setting text:", textError); - // Continue with connection even if text setting fails - results.push({ - id: clonedConnector.id, - startNodeId: startNodeId, - endNodeId: endNodeId, - text: "", - textError: textError.message - }); - - // Continue to next connection - continue; - } - } - - // Add to results (using the *original* IDs for reference if needed) - results.push({ - id: clonedConnector.id, - originalStartNodeId: originalStartId, - originalEndNodeId: originalEndId, - usedStartNodeId: startId, // ID actually used for connection - usedEndNodeId: endId, // ID actually used for connection - text: text || "" - }); - - // Update progress - processedCount++; - sendProgressUpdate( - commandId, - "create_connections", - "in_progress", - processedCount / totalCount, - totalCount, - processedCount, - `Created connection ${processedCount}/${totalCount}` - ); - - } catch (error) { - console.error("Error creating connection", error); - // Continue processing remaining connections even if an error occurs - processedCount++; - sendProgressUpdate( - commandId, - "create_connections", - "in_progress", - processedCount / totalCount, - totalCount, - processedCount, - `Error creating connection: ${error.message}` - ); - - results.push({ - error: error.message, - connectionInfo: connections[i] - }); - } - } - - // Completion update - sendProgressUpdate( - commandId, - "create_connections", - "completed", - 1, - totalCount, - totalCount, - `Completed creating ${results.length} connections` - ); - - return { - success: true, - count: results.length, - connections: results - }; -} +// async function createConnections(params) { +// if (!params || !params.connections || !Array.isArray(params.connections)) { +// throw new Error('Missing or invalid connections parameter'); +// } +// +// const { connections } = params; +// +// // Command ID for progress tracking +// const commandId = generateCommandId(); +// sendProgressUpdate( +// commandId, +// "create_connections", +// "started", +// 0, +// connections.length, +// 0, +// `Starting to create ${connections.length} connections` +// ); +// +// // Get default connector ID from client storage +// const defaultConnectorId = await figma.clientStorage.getAsync('defaultConnectorId'); +// if (!defaultConnectorId) { +// throw new Error('No default connector set. Please try one of the following options to create connections:\n1. Create a connector in FigJam and copy/paste it to your current page, then run the "set_default_connector" command.\n2. Select an existing connector on the current page, then run the "set_default_connector" command.'); +// } +// +// // Get the default connector +// const defaultConnector = await figma.getNodeByIdAsync(defaultConnectorId); +// if (!defaultConnector) { +// throw new Error(`Default connector not found with ID: ${defaultConnectorId}`); +// } +// if (defaultConnector.type !== 'CONNECTOR') { +// throw new Error(`Node is not a connector: ${defaultConnectorId}`); +// } +// +// // Results array for connection creation +// const results = []; +// let processedCount = 0; +// const totalCount = connections.length; +// +// // Preload fonts (used for text if provided) +// let fontLoaded = false; +// +// for (let i = 0; i < connections.length; i++) { +// try { +// const { startNodeId: originalStartId, endNodeId: originalEndId, text } = connections[i]; +// let startId = originalStartId; +// let endId = originalEndId; +// +// // Check and potentially replace start node ID +// if (startId.includes(';')) { +// console.log(`Nested start node detected: ${startId}. Creating cursor node.`); +// const cursorResult = await createCursorNode(startId); +// if (!cursorResult || !cursorResult.id) { +// throw new Error(`Failed to create cursor node for nested start node: ${startId}`); +// } +// startId = cursorResult.id; +// } +// +// const startNode = await figma.getNodeByIdAsync(startId); +// if (!startNode) throw new Error(`Start node not found with ID: ${startId}`); +// +// // Check and potentially replace end node ID +// if (endId.includes(';')) { +// console.log(`Nested end node detected: ${endId}. Creating cursor node.`); +// const cursorResult = await createCursorNode(endId); +// if (!cursorResult || !cursorResult.id) { +// throw new Error(`Failed to create cursor node for nested end node: ${endId}`); +// } +// endId = cursorResult.id; +// } +// const endNode = await figma.getNodeByIdAsync(endId); +// if (!endNode) throw new Error(`End node not found with ID: ${endId}`); +// +// +// // Clone the default connector +// const clonedConnector = defaultConnector.clone(); +// +// // Update connector name using potentially replaced node names +// clonedConnector.name = `TTF_Connector/${startNode.id}/${endNode.id}`; +// +// // Set start and end points using potentially replaced IDs +// clonedConnector.connectorStart = { +// endpointNodeId: startId, +// magnet: 'AUTO' +// }; +// +// clonedConnector.connectorEnd = { +// endpointNodeId: endId, +// magnet: 'AUTO' +// }; +// +// // Add text (if provided) +// if (text) { +// try { +// // Try to load the necessary fonts +// try { +// // First check if default connector has font and use the same +// if (defaultConnector.text && defaultConnector.text.fontName) { +// const fontName = defaultConnector.text.fontName; +// await figma.loadFontAsync(fontName); +// clonedConnector.text.fontName = fontName; +// } else { +// // Try default Inter font +// await figma.loadFontAsync({ family: "Inter", style: "Regular" }); +// } +// } catch (fontError) { +// // If first font load fails, try another font style +// try { +// await figma.loadFontAsync({ family: "Inter", style: "Medium" }); +// } catch (mediumFontError) { +// // If second font fails, try system font +// try { +// await figma.loadFontAsync({ family: "System", style: "Regular" }); +// } catch (systemFontError) { +// // If all font loading attempts fail, throw error +// throw new Error(`Failed to load any font: ${fontError.message}`); +// } +// } +// } +// +// // Set the text +// clonedConnector.text.characters = text; +// } catch (textError) { +// console.error("Error setting text:", textError); +// // Continue with connection even if text setting fails +// results.push({ +// id: clonedConnector.id, +// startNodeId: startNodeId, +// endNodeId: endNodeId, +// text: "", +// textError: textError.message +// }); +// +// // Continue to next connection +// continue; +// } +// } +// +// // Add to results (using the *original* IDs for reference if needed) +// results.push({ +// id: clonedConnector.id, +// originalStartNodeId: originalStartId, +// originalEndNodeId: originalEndId, +// usedStartNodeId: startId, // ID actually used for connection +// usedEndNodeId: endId, // ID actually used for connection +// text: text || "" +// }); +// +// // Update progress +// processedCount++; +// sendProgressUpdate( +// commandId, +// "create_connections", +// "in_progress", +// processedCount / totalCount, +// totalCount, +// processedCount, +// `Created connection ${processedCount}/${totalCount}` +// ); +// +// } catch (error) { +// console.error("Error creating connection", error); +// // Continue processing remaining connections even if an error occurs +// processedCount++; +// sendProgressUpdate( +// commandId, +// "create_connections", +// "in_progress", +// processedCount / totalCount, +// totalCount, +// processedCount, +// `Error creating connection: ${error.message}` +// ); +// +// results.push({ +// error: error.message, +// connectionInfo: connections[i] +// }); +// } +// } +// +// // Completion update +// sendProgressUpdate( +// commandId, +// "create_connections", +// "completed", +// 1, +// totalCount, +// totalCount, +// `Completed creating ${results.length} connections` +// ); +// +// return { +// success: true, +// count: results.length, +// connections: results +// }; +// } // Set focus on a specific node async function setFocus(params) { diff --git a/src/talk_to_figma_mcp/server.ts b/src/talk_to_figma_mcp/server.ts index 7b16785..781bb52 100644 --- a/src/talk_to_figma_mcp/server.ts +++ b/src/talk_to_figma_mcp/server.ts @@ -2338,7 +2338,7 @@ server.tool( // A tool to get Figma Prototyping Reactions from multiple nodes server.tool( "get_reactions", - "Get Figma Prototyping Reactions from multiple nodes. CRITICAL: The output MUST be processed using the 'reaction_to_connector_strategy' prompt IMMEDIATELY to generate parameters for connector lines via the 'create_connections' tool.", + "Get Figma Prototyping Reactions from multiple nodes for analyzing prototype flows and interactions.", { nodeIds: z.array(z.string()).describe("Array of node IDs to get reactions from"), }, @@ -2350,16 +2350,18 @@ server.tool( { type: "text", text: JSON.stringify(result) - }, - { - type: "text", - text: "IMPORTANT: You MUST now use the reaction data above and follow the `reaction_to_connector_strategy` prompt to prepare the parameters for the `create_connections` tool call. This is a required next step." } + // NOTE: Connector functionality has been disabled due to Figma API deprecation + // { + // type: "text", + // text: "IMPORTANT: You MUST now use the reaction data above and follow the `reaction_to_connector_strategy` prompt to prepare the parameters for the `create_connections` tool call. This is a required next step." + // } ], - followUp: { - type: "prompt", - prompt: "reaction_to_connector_strategy", - }, + // NOTE: Connector followUp disabled due to Figma API deprecation + // followUp: { + // type: "prompt", + // prompt: "reaction_to_connector_strategy", + // }, }; } catch (error) { return { @@ -2375,88 +2377,92 @@ server.tool( } ); -// Create Connectors Tool -server.tool( - "set_default_connector", - "Set a copied connector node as the default connector", - { - connectorId: z.string().optional().describe("The ID of the connector node to set as default") - }, - async ({ connectorId }: any) => { - try { - const result = await sendCommandToFigma("set_default_connector", { - connectorId - }); - - return { - content: [ - { - type: "text", - text: `Default connector set: ${JSON.stringify(result)}` - } - ] - }; - } catch (error) { - return { - content: [ - { - type: "text", - text: `Error setting default connector: ${error instanceof Error ? error.message : String(error)}` - } - ] - }; - } - } -); - -// Connect Nodes Tool -server.tool( - "create_connections", - "Create connections between nodes using the default connector style", - { - connections: z.array(z.object({ - startNodeId: z.string().describe("ID of the starting node"), - endNodeId: z.string().describe("ID of the ending node"), - text: z.string().optional().describe("Optional text to display on the connector") - })).describe("Array of node connections to create") - }, - async ({ connections }: any) => { - try { - if (!connections || connections.length === 0) { - return { - content: [ - { - type: "text", - text: "No connections provided" - } - ] - }; - } - - const result = await sendCommandToFigma("create_connections", { - connections - }); +// NOTE: Connector tools have been disabled due to Figma API deprecation +// Figma has officially blocked the FigJam Connector API +// See: https://github.com/grab/cursor-talk-to-figma-mcp/issues/106 - return { - content: [ - { - type: "text", - text: `Created ${connections.length} connections: ${JSON.stringify(result)}` - } - ] - }; - } catch (error) { - return { - content: [ - { - type: "text", - text: `Error creating connections: ${error instanceof Error ? error.message : String(error)}` - } - ] - }; - } - } -); +// Create Connectors Tool +// server.tool( +// "set_default_connector", +// "Set a copied connector node as the default connector", +// { +// connectorId: z.string().optional().describe("The ID of the connector node to set as default") +// }, +// async ({ connectorId }: any) => { +// try { +// const result = await sendCommandToFigma("set_default_connector", { +// connectorId +// }); +// +// return { +// content: [ +// { +// type: "text", +// text: `Default connector set: ${JSON.stringify(result)}` +// } +// ] +// }; +// } catch (error) { +// return { +// content: [ +// { +// type: "text", +// text: `Error setting default connector: ${error instanceof Error ? error.message : String(error)}` +// } +// ] +// }; +// } +// } +// ); +// +// // Connect Nodes Tool +// server.tool( +// "create_connections", +// "Create connections between nodes using the default connector style", +// { +// connections: z.array(z.object({ +// startNodeId: z.string().describe("ID of the starting node"), +// endNodeId: z.string().describe("ID of the ending node"), +// text: z.string().optional().describe("Optional text to display on the connector") +// })).describe("Array of node connections to create") +// }, +// async ({ connections }: any) => { +// try { +// if (!connections || connections.length === 0) { +// return { +// content: [ +// { +// type: "text", +// text: "No connections provided" +// } +// ] +// }; +// } +// +// const result = await sendCommandToFigma("create_connections", { +// connections +// }); +// +// return { +// content: [ +// { +// type: "text", +// text: `Created ${connections.length} connections: ${JSON.stringify(result)}` +// } +// ] +// }; +// } catch (error) { +// return { +// content: [ +// { +// type: "text", +// text: `Error creating connections: ${error instanceof Error ? error.message : String(error)}` +// } +// ] +// }; +// } +// } +// ); // Set Focus Tool server.tool( @@ -2522,89 +2528,93 @@ server.tool( } ); -// Strategy for converting Figma prototype reactions to connector lines -server.prompt( - "reaction_to_connector_strategy", - "Strategy for converting Figma prototype reactions to connector lines using the output of 'get_reactions'", - (extra) => { - return { - messages: [ - { - role: "assistant", - content: { - type: "text", - text: `# Strategy: Convert Figma Prototype Reactions to Connector Lines - -## Goal -Process the JSON output from the \`get_reactions\` tool to generate an array of connection objects suitable for the \`create_connections\` tool. This visually represents prototype flows as connector lines on the Figma canvas. - -## Input Data -You will receive JSON data from the \`get_reactions\` tool. This data contains an array of nodes, each with potential reactions. A typical reaction object looks like this: -\`\`\`json -{ - "trigger": { "type": "ON_CLICK" }, - "action": { - "type": "NAVIGATE", - "destinationId": "destination-node-id", - "navigationTransition": { ... }, - "preserveScrollPosition": false - } -} -\`\`\` +// NOTE: Connector strategy has been disabled due to Figma API deprecation +// Figma has officially blocked the FigJam Connector API +// See: https://github.com/grab/cursor-talk-to-figma-mcp/issues/106 -## Step-by-Step Process - -### 1. Preparation & Context Gathering - - **Action:** Call \`read_my_design\` on the relevant node(s) to get context about the nodes involved (names, types, etc.). This helps in generating meaningful connector labels later. - - **Action:** Call \`set_default_connector\` **without** the \`connectorId\` parameter. - - **Check Result:** Analyze the response from \`set_default_connector\`. - - If it confirms a default connector is already set (e.g., "Default connector is already set"), proceed to Step 2. - - If it indicates no default connector is set (e.g., "No default connector set..."), you **cannot** proceed with \`create_connections\` yet. Inform the user they need to manually copy a connector from FigJam, paste it onto the current page, select it, and then you can run \`set_default_connector({ connectorId: "SELECTED_NODE_ID" })\` before attempting \`create_connections\`. **Do not proceed to Step 2 until a default connector is confirmed.** - -### 2. Filter and Transform Reactions from \`get_reactions\` Output - - **Iterate:** Go through the JSON array provided by \`get_reactions\`. For each node in the array: - - Iterate through its \`reactions\` array. - - **Filter:** Keep only reactions where the \`action\` meets these criteria: - - Has a \`type\` that implies a connection (e.g., \`NAVIGATE\`, \`OPEN_OVERLAY\`, \`SWAP_OVERLAY\`). **Ignore** types like \`CHANGE_TO\`, \`CLOSE_OVERLAY\`, etc. - - Has a valid \`destinationId\` property. - - **Extract:** For each valid reaction, extract the following information: - - \`sourceNodeId\`: The ID of the node the reaction belongs to (from the outer loop). - - \`destinationNodeId\`: The value of \`action.destinationId\`. - - \`actionType\`: The value of \`action.type\`. - - \`triggerType\`: The value of \`trigger.type\`. - -### 3. Generate Connector Text Labels - - **For each extracted connection:** Create a concise, descriptive text label string. - - **Combine Information:** Use the \`actionType\`, \`triggerType\`, and potentially the names of the source/destination nodes (obtained from Step 1's \`read_my_design\` or by calling \`get_node_info\` if necessary) to generate the label. - - **Example Labels:** - - If \`triggerType\` is "ON\_CLICK" and \`actionType\` is "NAVIGATE": "On click, navigate to [Destination Node Name]" - - If \`triggerType\` is "ON\_DRAG" and \`actionType\` is "OPEN\_OVERLAY": "On drag, open [Destination Node Name] overlay" - - **Keep it brief and informative.** Let this generated string be \`generatedText\`. - -### 4. Prepare the \`connections\` Array for \`create_connections\` - - **Structure:** Create a JSON array where each element is an object representing a connection. - - **Format:** Each object in the array must have the following structure: - \`\`\`json - { - "startNodeId": "sourceNodeId_from_step_2", - "endNodeId": "destinationNodeId_from_step_2", - "text": "generatedText_from_step_3" - } - \`\`\` - - **Result:** This final array is the value you will pass to the \`connections\` parameter when calling the \`create_connections\` tool. - -### 5. Execute Connection Creation - - **Action:** Call the \`create_connections\` tool, passing the array generated in Step 4 as the \`connections\` argument. - - **Verify:** Check the response from \`create_connections\` to confirm success or failure. - -This detailed process ensures you correctly interpret the reaction data, prepare the necessary information, and use the appropriate tools to create the connector lines.` - }, - }, - ], - description: "Strategy for converting Figma prototype reactions to connector lines using the output of 'get_reactions'", - }; - } -); +// Strategy for converting Figma prototype reactions to connector lines +// server.prompt( +// "reaction_to_connector_strategy", +// "Strategy for converting Figma prototype reactions to connector lines using the output of 'get_reactions'", +// (extra) => { +// return { +// messages: [ +// { +// role: "assistant", +// content: { +// type: "text", +// text: `# Strategy: Convert Figma Prototype Reactions to Connector Lines +// +// ## Goal +// Process the JSON output from the \`get_reactions\` tool to generate an array of connection objects suitable for the \`create_connections\` tool. This visually represents prototype flows as connector lines on the Figma canvas. +// +// ## Input Data +// You will receive JSON data from the \`get_reactions\` tool. This data contains an array of nodes, each with potential reactions. A typical reaction object looks like this: +// \`\`\`json +// { +// "trigger": { "type": "ON_CLICK" }, +// "action": { +// "type": "NAVIGATE", +// "destinationId": "destination-node-id", +// "navigationTransition": { ... }, +// "preserveScrollPosition": false +// } +// } +// \`\`\` +// +// ## Step-by-Step Process +// +// ### 1. Preparation & Context Gathering +// - **Action:** Call \`read_my_design\` on the relevant node(s) to get context about the nodes involved (names, types, etc.). This helps in generating meaningful connector labels later. +// - **Action:** Call \`set_default_connector\` **without** the \`connectorId\` parameter. +// - **Check Result:** Analyze the response from \`set_default_connector\`. +// - If it confirms a default connector is already set (e.g., "Default connector is already set"), proceed to Step 2. +// - If it indicates no default connector is set (e.g., "No default connector set..."), you **cannot** proceed with \`create_connections\` yet. Inform the user they need to manually copy a connector from FigJam, paste it onto the current page, select it, and then you can run \`set_default_connector({ connectorId: "SELECTED_NODE_ID" })\` before attempting \`create_connections\`. **Do not proceed to Step 2 until a default connector is confirmed.** +// +// ### 2. Filter and Transform Reactions from \`get_reactions\` Output +// - **Iterate:** Go through the JSON array provided by \`get_reactions\`. For each node in the array: +// - Iterate through its \`reactions\` array. +// - **Filter:** Keep only reactions where the \`action\` meets these criteria: +// - Has a \`type\` that implies a connection (e.g., \`NAVIGATE\`, \`OPEN_OVERLAY\`, \`SWAP_OVERLAY\`). **Ignore** types like \`CHANGE_TO\`, \`CLOSE_OVERLAY\`, etc. +// - Has a valid \`destinationId\` property. +// - **Extract:** For each valid reaction, extract the following information: +// - \`sourceNodeId\`: The ID of the node the reaction belongs to (from the outer loop). +// - \`destinationNodeId\`: The value of \`action.destinationId\`. +// - \`actionType\`: The value of \`action.type\`. +// - \`triggerType\`: The value of \`trigger.type\`. +// +// ### 3. Generate Connector Text Labels +// - **For each extracted connection:** Create a concise, descriptive text label string. +// - **Combine Information:** Use the \`actionType\`, \`triggerType\`, and potentially the names of the source/destination nodes (obtained from Step 1's \`read_my_design\` or by calling \`get_node_info\` if necessary) to generate the label. +// - **Example Labels:** +// - If \`triggerType\` is "ON\_CLICK" and \`actionType\` is "NAVIGATE": "On click, navigate to [Destination Node Name]" +// - If \`triggerType\` is "ON\_DRAG" and \`actionType\` is "OPEN\_OVERLAY": "On drag, open [Destination Node Name] overlay" +// - **Keep it brief and informative.** Let this generated string be \`generatedText\`. +// +// ### 4. Prepare the \`connections\` Array for \`create_connections\` +// - **Structure:** Create a JSON array where each element is an object representing a connection. +// - **Format:** Each object in the array must have the following structure: +// \`\`\`json +// { +// "startNodeId": "sourceNodeId_from_step_2", +// "endNodeId": "destinationNodeId_from_step_2", +// "text": "generatedText_from_step_3" +// } +// \`\`\` +// - **Result:** This final array is the value you will pass to the \`connections\` parameter when calling the \`create_connections\` tool. +// +// ### 5. Execute Connection Creation +// - **Action:** Call the \`create_connections\` tool, passing the array generated in Step 4 as the \`connections\` argument. +// - **Verify:** Check the response from \`create_connections\` to confirm success or failure. +// +// This detailed process ensures you correctly interpret the reaction data, prepare the necessary information, and use the appropriate tools to create the connector lines.` +// }, +// }, +// ], +// description: "Strategy for converting Figma prototype reactions to connector lines using the output of 'get_reactions'", +// }; +// } +// ); // Define command types and parameters @@ -2645,8 +2655,9 @@ type FigmaCommand = | "set_layout_sizing" | "set_item_spacing" | "get_reactions" - | "set_default_connector" - | "create_connections" + // NOTE: Connector commands disabled due to Figma API deprecation + // | "set_default_connector" + // | "create_connections" | "set_focus" | "set_selections"; @@ -2781,16 +2792,17 @@ type CommandParams = { types: Array; }; get_reactions: { nodeIds: string[] }; - set_default_connector: { - connectorId?: string | undefined; - }; - create_connections: { - connections: Array<{ - startNodeId: string; - endNodeId: string; - text?: string; - }>; - }; + // NOTE: Connector parameters disabled due to Figma API deprecation + // set_default_connector: { + // connectorId?: string | undefined; + // }; + // create_connections: { + // connections: Array<{ + // startNodeId: string; + // endNodeId: string; + // text?: string; + // }>; + // }; set_focus: { nodeId: string; }; From beb2e881b9423533c7902e2e76ec27ba59cffab2 Mon Sep 17 00:00:00 2001 From: JooHyung Park Date: Fri, 5 Sep 2025 14:54:15 +0900 Subject: [PATCH 7/8] Update code.js --- src/cursor_mcp_plugin/code.js | 297 ---------------------------------- 1 file changed, 297 deletions(-) diff --git a/src/cursor_mcp_plugin/code.js b/src/cursor_mcp_plugin/code.js index 8e7ca89..682347e 100644 --- a/src/cursor_mcp_plugin/code.js +++ b/src/cursor_mcp_plugin/code.js @@ -225,11 +225,6 @@ async function handleCommand(command, params) { throw new Error("Missing or invalid nodeIds parameter"); } return await getReactions(params.nodeIds); - // NOTE: Connector functionality disabled due to Figma API deprecation - // case "set_default_connector": - // return await setDefaultConnector(params); - // case "create_connections": - // return await createConnections(params); case "set_focus": return await setFocus(params); case "set_selections": @@ -3563,96 +3558,6 @@ async function setItemSpacing(params) { }; } -// NOTE: Connector functionality has been disabled due to Figma API deprecation -// Figma has officially blocked the FigJam Connector API -// See: https://github.com/grab/cursor-talk-to-figma-mcp/issues/106 - -// async function setDefaultConnector(params) { -// const { connectorId } = params || {}; -// -// // If connectorId is provided, search and set by that ID (do not check existing storage) -// if (connectorId) { -// // Get node by specified ID -// const node = await figma.getNodeByIdAsync(connectorId); -// if (!node) { -// throw new Error(`Connector node not found with ID: ${connectorId}`); -// } -// -// // Check node type -// if (node.type !== 'CONNECTOR') { -// throw new Error(`Node is not a connector: ${connectorId}`); -// } -// -// // Set the found connector as the default connector -// await figma.clientStorage.setAsync('defaultConnectorId', connectorId); -// -// return { -// success: true, -// message: `Default connector set to: ${connectorId}`, -// connectorId: connectorId -// }; -// } -// // If connectorId is not provided, check existing storage -// else { -// // Check if there is an existing default connector in client storage -// try { -// const existingConnectorId = await figma.clientStorage.getAsync('defaultConnectorId'); -// -// // If there is an existing connector ID, check if the node is still valid -// if (existingConnectorId) { -// try { -// const existingConnector = await figma.getNodeByIdAsync(existingConnectorId); -// -// // If the stored connector still exists and is of type CONNECTOR -// if (existingConnector && existingConnector.type === 'CONNECTOR') { -// return { -// success: true, -// message: `Default connector is already set to: ${existingConnectorId}`, -// connectorId: existingConnectorId, -// exists: true -// }; -// } -// // The stored connector is no longer valid - find a new connector -// else { -// console.log(`Stored connector ID ${existingConnectorId} is no longer valid, finding a new connector...`); -// } -// } catch (error) { -// console.log(`Error finding stored connector: ${error.message}. Will try to set a new one.`); -// } -// } -// } catch (error) { -// console.log(`Error checking for existing connector: ${error.message}`); -// } -// -// // If there is no stored default connector or it is invalid, find one in the current page -// try { -// // Find CONNECTOR type nodes in the current page -// const currentPageConnectors = figma.currentPage.findAllWithCriteria({ types: ['CONNECTOR'] }); -// -// if (currentPageConnectors && currentPageConnectors.length > 0) { -// // Use the first connector found -// const foundConnector = currentPageConnectors[0]; -// const autoFoundId = foundConnector.id; -// -// // Set the found connector as the default connector -// await figma.clientStorage.setAsync('defaultConnectorId', autoFoundId); -// -// return { -// success: true, -// message: `Automatically found and set default connector to: ${autoFoundId}`, -// connectorId: autoFoundId, -// autoSelected: true -// }; -// } else { -// // If no connector is found in the current page, show a guide message -// throw new Error('No connector found in the current page. Please create a connector in Figma first or specify a connector ID.'); -// } -// } catch (error) { -// // Error occurred while running findAllWithCriteria -// throw new Error(`Failed to find a connector: ${error.message}`); -// } -// } -// } async function createCursorNode(targetNodeId) { const svgString = ` @@ -3761,208 +3666,6 @@ async function createCursorNode(targetNodeId) { } } -// NOTE: Connector functionality has been disabled due to Figma API deprecation -// Figma has officially blocked the FigJam Connector API -// See: https://github.com/grab/cursor-talk-to-figma-mcp/issues/106 - -// async function createConnections(params) { -// if (!params || !params.connections || !Array.isArray(params.connections)) { -// throw new Error('Missing or invalid connections parameter'); -// } -// -// const { connections } = params; -// -// // Command ID for progress tracking -// const commandId = generateCommandId(); -// sendProgressUpdate( -// commandId, -// "create_connections", -// "started", -// 0, -// connections.length, -// 0, -// `Starting to create ${connections.length} connections` -// ); -// -// // Get default connector ID from client storage -// const defaultConnectorId = await figma.clientStorage.getAsync('defaultConnectorId'); -// if (!defaultConnectorId) { -// throw new Error('No default connector set. Please try one of the following options to create connections:\n1. Create a connector in FigJam and copy/paste it to your current page, then run the "set_default_connector" command.\n2. Select an existing connector on the current page, then run the "set_default_connector" command.'); -// } -// -// // Get the default connector -// const defaultConnector = await figma.getNodeByIdAsync(defaultConnectorId); -// if (!defaultConnector) { -// throw new Error(`Default connector not found with ID: ${defaultConnectorId}`); -// } -// if (defaultConnector.type !== 'CONNECTOR') { -// throw new Error(`Node is not a connector: ${defaultConnectorId}`); -// } -// -// // Results array for connection creation -// const results = []; -// let processedCount = 0; -// const totalCount = connections.length; -// -// // Preload fonts (used for text if provided) -// let fontLoaded = false; -// -// for (let i = 0; i < connections.length; i++) { -// try { -// const { startNodeId: originalStartId, endNodeId: originalEndId, text } = connections[i]; -// let startId = originalStartId; -// let endId = originalEndId; -// -// // Check and potentially replace start node ID -// if (startId.includes(';')) { -// console.log(`Nested start node detected: ${startId}. Creating cursor node.`); -// const cursorResult = await createCursorNode(startId); -// if (!cursorResult || !cursorResult.id) { -// throw new Error(`Failed to create cursor node for nested start node: ${startId}`); -// } -// startId = cursorResult.id; -// } -// -// const startNode = await figma.getNodeByIdAsync(startId); -// if (!startNode) throw new Error(`Start node not found with ID: ${startId}`); -// -// // Check and potentially replace end node ID -// if (endId.includes(';')) { -// console.log(`Nested end node detected: ${endId}. Creating cursor node.`); -// const cursorResult = await createCursorNode(endId); -// if (!cursorResult || !cursorResult.id) { -// throw new Error(`Failed to create cursor node for nested end node: ${endId}`); -// } -// endId = cursorResult.id; -// } -// const endNode = await figma.getNodeByIdAsync(endId); -// if (!endNode) throw new Error(`End node not found with ID: ${endId}`); -// -// -// // Clone the default connector -// const clonedConnector = defaultConnector.clone(); -// -// // Update connector name using potentially replaced node names -// clonedConnector.name = `TTF_Connector/${startNode.id}/${endNode.id}`; -// -// // Set start and end points using potentially replaced IDs -// clonedConnector.connectorStart = { -// endpointNodeId: startId, -// magnet: 'AUTO' -// }; -// -// clonedConnector.connectorEnd = { -// endpointNodeId: endId, -// magnet: 'AUTO' -// }; -// -// // Add text (if provided) -// if (text) { -// try { -// // Try to load the necessary fonts -// try { -// // First check if default connector has font and use the same -// if (defaultConnector.text && defaultConnector.text.fontName) { -// const fontName = defaultConnector.text.fontName; -// await figma.loadFontAsync(fontName); -// clonedConnector.text.fontName = fontName; -// } else { -// // Try default Inter font -// await figma.loadFontAsync({ family: "Inter", style: "Regular" }); -// } -// } catch (fontError) { -// // If first font load fails, try another font style -// try { -// await figma.loadFontAsync({ family: "Inter", style: "Medium" }); -// } catch (mediumFontError) { -// // If second font fails, try system font -// try { -// await figma.loadFontAsync({ family: "System", style: "Regular" }); -// } catch (systemFontError) { -// // If all font loading attempts fail, throw error -// throw new Error(`Failed to load any font: ${fontError.message}`); -// } -// } -// } -// -// // Set the text -// clonedConnector.text.characters = text; -// } catch (textError) { -// console.error("Error setting text:", textError); -// // Continue with connection even if text setting fails -// results.push({ -// id: clonedConnector.id, -// startNodeId: startNodeId, -// endNodeId: endNodeId, -// text: "", -// textError: textError.message -// }); -// -// // Continue to next connection -// continue; -// } -// } -// -// // Add to results (using the *original* IDs for reference if needed) -// results.push({ -// id: clonedConnector.id, -// originalStartNodeId: originalStartId, -// originalEndNodeId: originalEndId, -// usedStartNodeId: startId, // ID actually used for connection -// usedEndNodeId: endId, // ID actually used for connection -// text: text || "" -// }); -// -// // Update progress -// processedCount++; -// sendProgressUpdate( -// commandId, -// "create_connections", -// "in_progress", -// processedCount / totalCount, -// totalCount, -// processedCount, -// `Created connection ${processedCount}/${totalCount}` -// ); -// -// } catch (error) { -// console.error("Error creating connection", error); -// // Continue processing remaining connections even if an error occurs -// processedCount++; -// sendProgressUpdate( -// commandId, -// "create_connections", -// "in_progress", -// processedCount / totalCount, -// totalCount, -// processedCount, -// `Error creating connection: ${error.message}` -// ); -// -// results.push({ -// error: error.message, -// connectionInfo: connections[i] -// }); -// } -// } -// -// // Completion update -// sendProgressUpdate( -// commandId, -// "create_connections", -// "completed", -// 1, -// totalCount, -// totalCount, -// `Completed creating ${results.length} connections` -// ); -// -// return { -// success: true, -// count: results.length, -// connections: results -// }; -// } // Set focus on a specific node async function setFocus(params) { From b231279c6b0e9db2e7a75bf92cfe241feebf7265 Mon Sep 17 00:00:00 2001 From: JooHyung Park Date: Fri, 5 Sep 2025 14:54:19 +0900 Subject: [PATCH 8/8] Update server.ts --- src/talk_to_figma_mcp/server.ts | 197 -------------------------------- 1 file changed, 197 deletions(-) diff --git a/src/talk_to_figma_mcp/server.ts b/src/talk_to_figma_mcp/server.ts index 781bb52..bfbde67 100644 --- a/src/talk_to_figma_mcp/server.ts +++ b/src/talk_to_figma_mcp/server.ts @@ -2351,17 +2351,7 @@ server.tool( type: "text", text: JSON.stringify(result) } - // NOTE: Connector functionality has been disabled due to Figma API deprecation - // { - // type: "text", - // text: "IMPORTANT: You MUST now use the reaction data above and follow the `reaction_to_connector_strategy` prompt to prepare the parameters for the `create_connections` tool call. This is a required next step." - // } ], - // NOTE: Connector followUp disabled due to Figma API deprecation - // followUp: { - // type: "prompt", - // prompt: "reaction_to_connector_strategy", - // }, }; } catch (error) { return { @@ -2377,92 +2367,6 @@ server.tool( } ); -// NOTE: Connector tools have been disabled due to Figma API deprecation -// Figma has officially blocked the FigJam Connector API -// See: https://github.com/grab/cursor-talk-to-figma-mcp/issues/106 - -// Create Connectors Tool -// server.tool( -// "set_default_connector", -// "Set a copied connector node as the default connector", -// { -// connectorId: z.string().optional().describe("The ID of the connector node to set as default") -// }, -// async ({ connectorId }: any) => { -// try { -// const result = await sendCommandToFigma("set_default_connector", { -// connectorId -// }); -// -// return { -// content: [ -// { -// type: "text", -// text: `Default connector set: ${JSON.stringify(result)}` -// } -// ] -// }; -// } catch (error) { -// return { -// content: [ -// { -// type: "text", -// text: `Error setting default connector: ${error instanceof Error ? error.message : String(error)}` -// } -// ] -// }; -// } -// } -// ); -// -// // Connect Nodes Tool -// server.tool( -// "create_connections", -// "Create connections between nodes using the default connector style", -// { -// connections: z.array(z.object({ -// startNodeId: z.string().describe("ID of the starting node"), -// endNodeId: z.string().describe("ID of the ending node"), -// text: z.string().optional().describe("Optional text to display on the connector") -// })).describe("Array of node connections to create") -// }, -// async ({ connections }: any) => { -// try { -// if (!connections || connections.length === 0) { -// return { -// content: [ -// { -// type: "text", -// text: "No connections provided" -// } -// ] -// }; -// } -// -// const result = await sendCommandToFigma("create_connections", { -// connections -// }); -// -// return { -// content: [ -// { -// type: "text", -// text: `Created ${connections.length} connections: ${JSON.stringify(result)}` -// } -// ] -// }; -// } catch (error) { -// return { -// content: [ -// { -// type: "text", -// text: `Error creating connections: ${error instanceof Error ? error.message : String(error)}` -// } -// ] -// }; -// } -// } -// ); // Set Focus Tool server.tool( @@ -2528,93 +2432,6 @@ server.tool( } ); -// NOTE: Connector strategy has been disabled due to Figma API deprecation -// Figma has officially blocked the FigJam Connector API -// See: https://github.com/grab/cursor-talk-to-figma-mcp/issues/106 - -// Strategy for converting Figma prototype reactions to connector lines -// server.prompt( -// "reaction_to_connector_strategy", -// "Strategy for converting Figma prototype reactions to connector lines using the output of 'get_reactions'", -// (extra) => { -// return { -// messages: [ -// { -// role: "assistant", -// content: { -// type: "text", -// text: `# Strategy: Convert Figma Prototype Reactions to Connector Lines -// -// ## Goal -// Process the JSON output from the \`get_reactions\` tool to generate an array of connection objects suitable for the \`create_connections\` tool. This visually represents prototype flows as connector lines on the Figma canvas. -// -// ## Input Data -// You will receive JSON data from the \`get_reactions\` tool. This data contains an array of nodes, each with potential reactions. A typical reaction object looks like this: -// \`\`\`json -// { -// "trigger": { "type": "ON_CLICK" }, -// "action": { -// "type": "NAVIGATE", -// "destinationId": "destination-node-id", -// "navigationTransition": { ... }, -// "preserveScrollPosition": false -// } -// } -// \`\`\` -// -// ## Step-by-Step Process -// -// ### 1. Preparation & Context Gathering -// - **Action:** Call \`read_my_design\` on the relevant node(s) to get context about the nodes involved (names, types, etc.). This helps in generating meaningful connector labels later. -// - **Action:** Call \`set_default_connector\` **without** the \`connectorId\` parameter. -// - **Check Result:** Analyze the response from \`set_default_connector\`. -// - If it confirms a default connector is already set (e.g., "Default connector is already set"), proceed to Step 2. -// - If it indicates no default connector is set (e.g., "No default connector set..."), you **cannot** proceed with \`create_connections\` yet. Inform the user they need to manually copy a connector from FigJam, paste it onto the current page, select it, and then you can run \`set_default_connector({ connectorId: "SELECTED_NODE_ID" })\` before attempting \`create_connections\`. **Do not proceed to Step 2 until a default connector is confirmed.** -// -// ### 2. Filter and Transform Reactions from \`get_reactions\` Output -// - **Iterate:** Go through the JSON array provided by \`get_reactions\`. For each node in the array: -// - Iterate through its \`reactions\` array. -// - **Filter:** Keep only reactions where the \`action\` meets these criteria: -// - Has a \`type\` that implies a connection (e.g., \`NAVIGATE\`, \`OPEN_OVERLAY\`, \`SWAP_OVERLAY\`). **Ignore** types like \`CHANGE_TO\`, \`CLOSE_OVERLAY\`, etc. -// - Has a valid \`destinationId\` property. -// - **Extract:** For each valid reaction, extract the following information: -// - \`sourceNodeId\`: The ID of the node the reaction belongs to (from the outer loop). -// - \`destinationNodeId\`: The value of \`action.destinationId\`. -// - \`actionType\`: The value of \`action.type\`. -// - \`triggerType\`: The value of \`trigger.type\`. -// -// ### 3. Generate Connector Text Labels -// - **For each extracted connection:** Create a concise, descriptive text label string. -// - **Combine Information:** Use the \`actionType\`, \`triggerType\`, and potentially the names of the source/destination nodes (obtained from Step 1's \`read_my_design\` or by calling \`get_node_info\` if necessary) to generate the label. -// - **Example Labels:** -// - If \`triggerType\` is "ON\_CLICK" and \`actionType\` is "NAVIGATE": "On click, navigate to [Destination Node Name]" -// - If \`triggerType\` is "ON\_DRAG" and \`actionType\` is "OPEN\_OVERLAY": "On drag, open [Destination Node Name] overlay" -// - **Keep it brief and informative.** Let this generated string be \`generatedText\`. -// -// ### 4. Prepare the \`connections\` Array for \`create_connections\` -// - **Structure:** Create a JSON array where each element is an object representing a connection. -// - **Format:** Each object in the array must have the following structure: -// \`\`\`json -// { -// "startNodeId": "sourceNodeId_from_step_2", -// "endNodeId": "destinationNodeId_from_step_2", -// "text": "generatedText_from_step_3" -// } -// \`\`\` -// - **Result:** This final array is the value you will pass to the \`connections\` parameter when calling the \`create_connections\` tool. -// -// ### 5. Execute Connection Creation -// - **Action:** Call the \`create_connections\` tool, passing the array generated in Step 4 as the \`connections\` argument. -// - **Verify:** Check the response from \`create_connections\` to confirm success or failure. -// -// This detailed process ensures you correctly interpret the reaction data, prepare the necessary information, and use the appropriate tools to create the connector lines.` -// }, -// }, -// ], -// description: "Strategy for converting Figma prototype reactions to connector lines using the output of 'get_reactions'", -// }; -// } -// ); // Define command types and parameters @@ -2655,9 +2472,6 @@ type FigmaCommand = | "set_layout_sizing" | "set_item_spacing" | "get_reactions" - // NOTE: Connector commands disabled due to Figma API deprecation - // | "set_default_connector" - // | "create_connections" | "set_focus" | "set_selections"; @@ -2792,17 +2606,6 @@ type CommandParams = { types: Array; }; get_reactions: { nodeIds: string[] }; - // NOTE: Connector parameters disabled due to Figma API deprecation - // set_default_connector: { - // connectorId?: string | undefined; - // }; - // create_connections: { - // connections: Array<{ - // startNodeId: string; - // endNodeId: string; - // text?: string; - // }>; - // }; set_focus: { nodeId: string; };