Cursor Talk to Figma MCP
Skill by ara.so — Design Skills collection.
This skill enables AI agents to communicate with Figma through the Model Context Protocol (MCP), allowing programmatic reading and modification of design files. The integration uses a WebSocket server to bridge AI agents (Cursor, Claude Code) with a Figma plugin.
Installation
Prerequisites
Install Bun runtime:
curl -fsSL https://bun.sh/install | bash
Quick Setup
Run the automated setup which installs the MCP server in your Cursor configuration:
bun setup
Manual MCP Configuration
Add to ~/.cursor/mcp.json:
{
"mcpServers": {
"TalkToFigma": {
"command": "bunx",
"args": ["cursor-talk-to-figma-mcp@latest"]
}
}
}
For local development, use direct file path:
{
"mcpServers": {
"TalkToFigma": {
"command": "bun",
"args": ["/absolute/path/to/src/talk_to_figma_mcp/server.ts"]
}
}
}
Start WebSocket Server
bun socket
Figma Plugin Installation
- Install from Figma Community
- Or develop locally: Plugins > Development > New Plugin > Link existing plugin > Select
src/cursor_mcp_plugin/manifest.json
Core Workflow
1. Establish Connection
// First, join a channel to communicate with Figma
await use_mcp_tool("TalkToFigma", "join_channel", {
channelId: "my-design-session"
});
2. Read Design Information
// Get document overview
const docInfo = await use_mcp_tool("TalkToFigma", "get_document_info", {});
// Get current selection
const selection = await use_mcp_tool("TalkToFigma", "get_selection", {});
// Get detailed node information
const nodeInfo = await use_mcp_tool("TalkToFigma", "get_node_info", {
nodeId: "123:456"
});
// Read current selection details without parameters
const selectionDetails = await use_mcp_tool("TalkToFigma", "read_my_design", {});
3. Create Elements
// Create a frame
const frame = await use_mcp_tool("TalkToFigma", "create_frame", {
x: 0,
y: 0,
width: 375,
height: 812,
name: "iPhone Frame"
});
// Create a rectangle
const rect = await use_mcp_tool("TalkToFigma", "create_rectangle", {
x: 20,
y: 20,
width: 335,
height: 200,
name: "Header Background"
});
// Create text
const text = await use_mcp_tool("TalkToFigma", "create_text", {
x: 40,
y: 100,
characters: "Welcome to the App",
fontSize: 24,
fontName: { family: "Inter", style: "Bold" },
name: "Header Title"
});
4. Modify Existing Elements
// Set fill color (RGBA 0-1 range)
await use_mcp_tool("TalkToFigma", "set_fill_color", {
nodeId: "123:456",
r: 0.2,
g: 0.4,
b: 0.8,
a: 1.0
});
// Set stroke
await use_mcp_tool("TalkToFigma", "set_stroke_color", {
nodeId: "123:456",
r: 0.1,
g: 0.1,
b: 0.1,
a: 1.0,
strokeWeight: 2
});
// Set corner radius
await use_mcp_tool("TalkToFigma", "set_corner_radius", {
nodeId: "123:456",
radius: 12
});
// Move node
await use_mcp_tool("TalkToFigma", "move_node", {
nodeId: "123:456",
x: 100,
y: 200
});
// Resize node
await use_mcp_tool("TalkToFigma", "resize_node", {
nodeId: "123:456",
width: 300,
height: 150
});
Auto Layout Configuration
// Set layout mode
await use_mcp_tool("TalkToFigma", "set_layout_mode", {
nodeId: "123:456",
layoutMode: "VERTICAL", // "NONE", "HORIZONTAL", "VERTICAL"
layoutWrap: "NO_WRAP" // "NO_WRAP", "WRAP"
});
// Set padding
await use_mcp_tool("TalkToFigma", "set_padding", {
nodeId: "123:456",
paddingTop: 24,
paddingRight: 16,
paddingBottom: 24,
paddingLeft: 16
});
// Set alignment
await use_mcp_tool("TalkToFigma", "set_axis_align", {
nodeId: "123:456",
primaryAxisAlignItems: "CENTER", // "MIN", "CENTER", "MAX", "SPACE_BETWEEN"
counterAxisAlignItems: "CENTER" // "MIN", "CENTER", "MAX", "BASELINE"
});
// Set sizing behavior
await use_mcp_tool("TalkToFigma", "set_layout_sizing", {
nodeId: "123:456",
layoutSizingHorizontal: "HUG", // "FIXED", "HUG", "FILL"
layoutSizingVertical: "FIXED"
});
// Set item spacing
await use_mcp_tool("TalkToFigma", "set_item_spacing", {
nodeId: "123:456",
itemSpacing: 12
});
Bulk Text Operations
Scan Text Nodes
// Scan all text nodes with chunking for large designs
const textNodes = await use_mcp_tool("TalkToFigma", "scan_text_nodes", {
chunkSize: 50, // Process 50 nodes at a time
startIndex: 0, // Start from beginning
parentNodeId: null // Scan entire document (or specify parent)
});
// Response includes:
// - nodes: Array of text node info
// - hasMore: Boolean indicating more chunks available
// - nextIndex: Index for next chunk
// - total: Total number of text nodes
Update Single Text
await use_mcp_tool("TalkToFigma", "set_text_content", {
nodeId: "123:456",
characters: "Updated heading text"
});
Batch Update Multiple Texts
await use_mcp_tool("TalkToFigma", "set_multiple_text_contents", {
updates: [
{ nodeId: "123:456", characters: "New Title" },
{ nodeId: "123:789", characters: "New Description" },
{ nodeId: "123:101", characters: "Updated Label" }
]
});
Annotations
Get Annotations
// Get all annotations in document
const annotations = await use_mcp_tool("TalkToFigma", "get_annotations", {});
// Get annotations for specific node
const nodeAnnotations = await use_mcp_tool("TalkToFigma", "get_annotations", {
nodeId: "123:456"
});
Create/Update Annotation
await use_mcp_tool("TalkToFigma", "set_annotation", {
nodeId: "123:456",
text: "This component needs **accessibility** improvements:\n- Add ARIA labels\n- Improve color contrast",
category: "DESIGN" // "DESIGN", "ENGINEERING", "CONTENT", "OTHER"
});
Batch Create Annotations
await use_mcp_tool("TalkToFigma", "set_multiple_annotations", {
annotations: [
{
nodeId: "123:456",
text: "Update to new brand colors",
category: "DESIGN"
},
{
nodeId: "123:789",
text: "Implement loading state",
category: "ENGINEERING"
}
]
});
Components and Instances
Get Components
const components = await use_mcp_tool("TalkToFigma", "get_local_components", {});
Create Component Instance
const instance = await use_mcp_tool("TalkToFigma", "create_component_instance", {
componentKey: "abc123def456",
x: 100,
y: 100
});
Instance Override Management
// Extract overrides from source instance
const overrides = await use_mcp_tool("TalkToFigma", "get_instance_overrides", {
sourceInstanceId: "123:456"
});
// Apply overrides to target instances
await use_mcp_tool("TalkToFigma", "set_instance_overrides", {
targetInstanceIds: ["123:789", "123:101"],
overrides: overrides.overrides
});
Prototyping and Connections
Get Prototype Reactions
// Get all prototype reactions with visual highlights
const reactions = await use_mcp_tool("TalkToFigma", "get_reactions", {});
// Response includes source/destination nodes and interaction details
Create FigJam Connectors
// First, copy a connector in FigJam and set it as default
await use_mcp_tool("TalkToFigma", "set_default_connector", {});
// Create connections based on prototype flow
await use_mcp_tool("TalkToFigma", "create_connections", {
connections: [
{ from: "123:456", to: "123:789" },
{ from: "123:789", to: "123:101" }
]
});
Selection and Focus
// Focus on specific node (selects and scrolls viewport)
await use_mcp_tool("TalkToFigma", "set_focus", {
nodeId: "123:456"
});
// Select multiple nodes
await use_mcp_tool("TalkToFigma", "set_selections", {
nodeIds: ["123:456", "123:789", "123:101"]
});
Node Management
Clone Nodes
const clone = await use_mcp_tool("TalkToFigma", "clone_node", {
nodeId: "123:456",
offsetX: 20, // Optional X offset
offsetY: 20 // Optional Y offset
});
Delete Nodes
// Delete single node
await use_mcp_tool("TalkToFigma", "delete_node", {
nodeId: "123:456"
});
// Delete multiple nodes efficiently
await use_mcp_tool("TalkToFigma", "delete_multiple_nodes", {
nodeIds: ["123:456", "123:789", "123:101"]
});
Get Multiple Nodes Info
const nodesInfo = await use_mcp_tool("TalkToFigma", "get_nodes_info", {
nodeIds: ["123:456", "123:789", "123:101"]
});
Export
// Export node as image
const imageData = await use_mcp_tool("TalkToFigma", "export_node_as_image", {
nodeId: "123:456",
format: "PNG", // "PNG", "JPG", "SVG", "PDF"
scale: 2 // Optional: 1, 2, 3, 4
});
// Returns base64 encoded image data
Scan Nodes by Type
// Find all nodes of specific types (useful for finding annotation targets)
const frames = await use_mcp_tool("TalkToFigma", "scan_nodes_by_types", {
types: ["FRAME", "COMPONENT", "INSTANCE"],
parentNodeId: null // Optional: limit to specific parent
});
Common Patterns
Pattern: Create a Card Component
// 1. Create container frame
const card = await use_mcp_tool("TalkToFigma", "create_frame", {
x: 0, y: 0, width: 300, height: 400, name: "Card"
});
// 2. Set auto layout
await use_mcp_tool("TalkToFigma", "set_layout_mode", {
nodeId: card.id, layoutMode: "VERTICAL"
});
await use_mcp_tool("TalkToFigma", "set_padding", {
nodeId: card.id,
paddingTop: 20, paddingRight: 20,
paddingBottom: 20, paddingLeft: 20
});
await use_mcp_tool("TalkToFigma", "set_item_spacing", {
nodeId: card.id, itemSpacing: 16
});
// 3. Style the card
await use_mcp_tool("TalkToFigma", "set_fill_color", {
nodeId: card.id, r: 1, g: 1, b: 1, a: 1
});
await use_mcp_tool("TalkToFigma", "set_corner_radius", {
nodeId: card.id, radius: 8
});
// 4. Add shadow via stroke as workaround
await use_mcp_tool("TalkToFigma", "set_stroke_color", {
nodeId: card.id, r: 0.9, g: 0.9, b: 0.9, a: 1, strokeWeight: 1
});
Pattern: Bulk Content Replacement
// 1. Scan all text nodes in chunks
let allTextNodes = [];
let hasMore = true;
let nextIndex = 0;
while (hasMore) {
const result = await use_mcp_tool("TalkToFigma", "scan_text_nodes", {
chunkSize: 50,
startIndex: nextIndex
});
allTextNodes = allTextNodes.concat(result.nodes);
hasMore = result.hasMore;
nextIndex = result.nextIndex;
}
// 2. Filter and prepare updates
const updates = allTextNodes
.filter(node => node.characters.includes("old-text"))
.map(node => ({
nodeId: node.id,
characters: node.characters.replace("old-text", "new-text")
}));
// 3. Batch update
await use_mcp_tool("TalkToFigma", "set_multiple_text_contents", {
updates: updates
});
Pattern: Convert Legacy Annotations
// 1. Scan for text nodes with numbered markers
const textNodes = await use_mcp_tool("TalkToFigma", "scan_text_nodes", {});
const markers = textNodes.nodes.filter(n => /^\d+\./.test(n.characters));
// 2. Find potential annotation targets
const targets = await use_mcp_tool("TalkToFigma", "scan_nodes_by_types", {
types: ["FRAME", "COMPONENT", "INSTANCE", "RECTANGLE"]
});
// 3. Match markers to targets (by proximity, naming, or path)
const annotations = markers.map(marker => {
const targetNode = findClosestTarget(marker, targets.nodes);
return {
nodeId: targetNode.id,
text: marker.characters.replace(/^\d+\.\s*/, ""),
category: "DESIGN"
};
});
// 4. Create native annotations
await use_mcp_tool("TalkToFigma", "set_multiple_annotations", {
annotations: annotations
});
// 5. Delete old marker nodes
await use_mcp_tool("TalkToFigma", "delete_multiple_nodes", {
nodeIds: markers.map(m => m.id)
});
Pattern: Prototype to Connector Visualization
// 1. Get prototype reactions
const reactions = await use_mcp_tool("TalkToFigma", "get_reactions", {});
// 2. Copy and set default connector style in FigJam
await use_mcp_tool("TalkToFigma", "set_default_connector", {});
// 3. Extract connections from reactions
const connections = reactions.reactions.map(r => ({
from: r.node.id,
to: r.action.destinationId
}));
// 4. Create connector lines
await use_mcp_tool("TalkToFigma", "create_connections", {
connections: connections
});
Windows + WSL Configuration
For Windows with WSL, uncomment the hostname in src/socket.ts:
// Uncomment for Windows WSL
hostname: "0.0.0.0",
Install Bun via PowerShell:
irm bun.sh/install.ps1|iex
Troubleshooting
WebSocket Connection Issues
- Ensure WebSocket server is running:
bun socket - Verify Figma plugin is active and connected
- Check that
join_channelwas called successfully - For WSL, ensure hostname is set to
0.0.0.0
MCP Server Not Found
- Verify
~/.cursor/mcp.jsonconfiguration is correct - Restart Cursor after configuration changes
- Check that Bun is installed and in PATH
- For local development, use absolute paths
Commands Fail Silently
- Always join a channel first with
join_channel - Verify node IDs exist using
get_document_infoorget_selection - Check WebSocket server logs for errors
- Ensure Figma file is open and plugin is running
Text Update Failures
- Verify node is actually a TEXT node type
- Check that text layer is not locked
- For batch updates, process in smaller chunks if needed
- Missing fonts may cause failures - ensure fonts are available
Color Values
All color values use 0-1 range (not 0-255):
- Convert:
rgbValue / 255 - Example: RGB(51, 102, 204) = r: 0.2, g: 0.4, b: 0.8
Auto Layout Constraints
set_layout_modemust be called before other auto-layout properties- Cannot set padding on non-auto-layout frames
- Item spacing only works in auto-layout frames
- Some sizing modes require specific layout configurations
Best Practices
- Always establish connection first - Call
join_channelbefore any other commands - Get context before modifying - Use
get_document_infoandget_selectionto understand structure - Use batch operations - Prefer
set_multiple_text_contentsover multipleset_text_contentcalls - Chunk large operations - Use chunking parameters for designs with many nodes
- Verify changes - Use
get_node_infoafter modifications to confirm success - Handle errors gracefully - All commands can throw exceptions, implement proper error handling
- Use component instances - Prefer instances over duplicating elements for consistency
- Organize with frames - Use frames with auto-layout for structured, responsive designs
- Clean up legacy elements - Delete old nodes after migrating to new patterns
- Test incrementally - Verify each step when building complex automation workflows