Skip to main content
Glama
index.js51.5 kB
/** * Tool registry - registers all Figma tools with the MCP server */ import { z } from 'zod'; import { handleGetContext } from './context.js'; import { handleListPages } from './pages.js'; import { handleGetNodes } from './nodes.js'; import { handleSetFills, handleSetStrokes, handleCreateRectangle, handleSetText, handleCloneNodes, handleDeleteNodes, handleMoveNodes, handleResizeNodes, handleSetOpacity, handleSetCornerRadius, handleGroupNodes, handleUngroupNodes, handleCreateFrame, handleCreateText, handleSetSelection, handleSetCurrentPage, handleExportNode, // Phase 2 commands handleCreateEllipse, handleSetEffects, handleSetAutoLayout, handleGetLocalStyles, handleApplyStyle, handleCreateComponent, handleCreateInstance, // Phase 3 commands handleGetLocalVariables, handleSearchVariables, handleSetVariable, handleCreateLine, handleSetConstraints, // Phase 4 commands handleCreatePolygon, handleBooleanOperation, handleZoomToNode, handleSetBlendMode, handleDetachInstance, // Phase 5 commands handleSetLayoutAlign, handleCreateVector, handleRenameNode, handleReorderNode, // Smart Query commands handleSearchNodes, handleSearchComponents, handleSearchStyles, handleGetChildren, // Design System Creation commands handleSetTextStyle, handleCreatePaintStyle, handleCreateTextStyle, handleCreateVariableCollection, handleCreateVariable, handleRenameVariable, handleDeleteVariables, handleDeleteVariableCollection, handleRenameVariableCollection, handleRenameMode, handleAddMode, handleDeleteMode, handleUnbindVariable, // Page Management commands handleCreatePage, handleRenamePage, handleDeletePage, handleReorderPage, // Node Structure commands handleReparentNodes, handleMoveToPage, // Instance commands handleSwapInstance, // Additional commands handleDuplicatePage, handleSetRotation, handleSetLayoutGrids, handleCombineAsVariants } from './mutations.js'; // Color schema for fill/stroke shorthand const colorSchema = z.union([ z.object({ color: z.string().describe('Hex color (e.g., "#FF0000" or "#FF0000FF" with alpha)') }), z.object({ r: z.number().min(0).max(1).describe('Red (0-1)'), g: z.number().min(0).max(1).describe('Green (0-1)'), b: z.number().min(0).max(1).describe('Blue (0-1)'), a: z.number().min(0).max(1).optional().describe('Alpha (0-1, optional)') }), z.array(z.any()).describe('Full Figma fills array') ]); /** * Register all tools with the MCP server * @param {McpServer} server - MCP Server instance * @param {FigmaBridge} bridge - Figma bridge instance */ export function registerTools(server, bridge) { // ============================================================ // Server Info // ============================================================ // figma_server_info - Get MCP server info including port server.tool( 'figma_server_info', 'Get information about the MCP server including the WebSocket port it is running on.', {}, async () => ({ content: [{ type: 'text', text: JSON.stringify({ port: bridge.port, connected: bridge.isConnected(), documentInfo: bridge.getDocumentInfo() }, null, 2) }] }) ); // ============================================================ // Query Tools // ============================================================ // figma_get_context - Get current document context server.tool( 'figma_get_context', 'Get the current Figma document context including file info, current page, and selection. Use this to understand what document is open and what the user has selected.', {}, async () => handleGetContext(bridge) ); // figma_list_pages - List all pages server.tool( 'figma_list_pages', 'List all pages in the current Figma document. Returns page IDs, names, and indicates which page is currently active.', {}, async () => handleListPages(bridge) ); // figma_get_nodes - Get node details by ID server.tool( 'figma_get_nodes', 'Get detailed information about specific Figma nodes by their IDs. Returns node properties including type, position, size, fills, strokes, and more. TIP: Use figma_search_nodes or figma_get_children FIRST to find node IDs efficiently, then use this tool only for nodes you need full details on.', { nodeIds: z.array(z.string()).describe('Array of Figma node IDs (e.g., ["1:23", "4:56"])'), depth: z.enum(['minimal', 'compact', 'full']).optional().default('full').describe('Detail level: "minimal" (~5 props: id, name, type, childIds), "compact" (~10 props: + position/size), "full" (all ~40 props). Use minimal/compact for tree traversal to reduce tokens.') }, async (args) => handleGetNodes(bridge, args) ); // ============================================================ // Mutation Tools // ============================================================ // figma_set_fills - Set fill colors on a node server.tool( 'figma_set_fills', 'Set fill color. Accepts hex shorthand or fills array.', { nodeId: z.string().describe('The node ID to modify'), fills: colorSchema.describe('Fill color - use { color: "#RRGGBB" } for simple colors') }, async (args) => handleSetFills(bridge, args) ); // figma_set_strokes - Set stroke colors on a node server.tool( 'figma_set_strokes', 'Set stroke color. Accepts hex shorthand or strokes array.', { nodeId: z.string().describe('The node ID to modify'), strokes: colorSchema.describe('Stroke color - use { color: "#RRGGBB" } for simple colors'), strokeWeight: z.number().optional().describe('Stroke weight in pixels') }, async (args) => handleSetStrokes(bridge, args) ); // figma_create_rectangle - Create a new rectangle server.tool( 'figma_create_rectangle', 'Create a rectangle.', { x: z.number().optional().default(0).describe('X position'), y: z.number().optional().default(0).describe('Y position'), width: z.number().optional().default(100).describe('Width in pixels'), height: z.number().optional().default(100).describe('Height in pixels'), name: z.string().optional().default('Rectangle').describe('Node name'), fills: colorSchema.optional().describe('Fill color'), parentId: z.string().optional().describe('Parent node ID (defaults to current page)') }, async (args) => handleCreateRectangle(bridge, args) ); // figma_set_text - Set text content on a text node server.tool( 'figma_set_text', 'Set text content. Auto-loads fonts.', { nodeId: z.string().describe('The text node ID to modify'), text: z.string().describe('The new text content') }, async (args) => handleSetText(bridge, args) ); // figma_clone_nodes - Clone/duplicate nodes server.tool( 'figma_clone_nodes', 'Duplicate nodes.', { nodeIds: z.array(z.string()).describe('Array of node IDs to clone'), parentId: z.string().optional().describe('Parent node ID for clones (optional)'), offset: z.object({ x: z.number().optional().default(20).describe('X offset from original'), y: z.number().optional().default(20).describe('Y offset from original') }).optional().describe('Position offset for cloned nodes') }, async (args) => handleCloneNodes(bridge, args) ); // ============================================================ // Node Manipulation Tools // ============================================================ // figma_delete_nodes - Delete nodes server.tool( 'figma_delete_nodes', 'Delete nodes.', { nodeIds: z.array(z.string()).describe('Array of node IDs to delete') }, async (args) => handleDeleteNodes(bridge, args) ); // figma_move_nodes - Move nodes server.tool( 'figma_move_nodes', 'Move nodes. Use relative=true for offset.', { nodeIds: z.array(z.string()).describe('Array of node IDs to move'), x: z.number().optional().describe('X position (absolute) or offset (if relative=true)'), y: z.number().optional().describe('Y position (absolute) or offset (if relative=true)'), relative: z.boolean().optional().default(false).describe('If true, x/y are offsets from current position') }, async (args) => handleMoveNodes(bridge, args) ); // figma_resize_nodes - Resize nodes server.tool( 'figma_resize_nodes', 'Resize one or more nodes. At least one dimension (width or height) must be provided.', { nodeIds: z.array(z.string()).describe('Array of node IDs to resize'), width: z.number().optional().describe('New width in pixels'), height: z.number().optional().describe('New height in pixels') }, async (args) => handleResizeNodes(bridge, args) ); // figma_set_opacity - Set node opacity server.tool( 'figma_set_opacity', 'Set opacity (0-1).', { nodeId: z.string().describe('The node ID to modify'), opacity: z.number().min(0).max(1).describe('Opacity value from 0 (transparent) to 1 (opaque)') }, async (args) => handleSetOpacity(bridge, args) ); // figma_set_corner_radius - Set corner radius server.tool( 'figma_set_corner_radius', 'Set corner radius. Use individual values for asymmetric.', { nodeId: z.string().describe('The node ID to modify'), radius: z.number().optional().describe('Uniform corner radius for all corners'), topLeft: z.number().optional().describe('Top-left corner radius'), topRight: z.number().optional().describe('Top-right corner radius'), bottomLeft: z.number().optional().describe('Bottom-left corner radius'), bottomRight: z.number().optional().describe('Bottom-right corner radius') }, async (args) => handleSetCornerRadius(bridge, args) ); // figma_group_nodes - Group nodes server.tool( 'figma_group_nodes', 'Group nodes.', { nodeIds: z.array(z.string()).describe('Array of node IDs to group together'), name: z.string().optional().default('Group').describe('Name for the new group') }, async (args) => handleGroupNodes(bridge, args) ); // figma_ungroup_nodes - Ungroup nodes server.tool( 'figma_ungroup_nodes', 'Ungroup nodes.', { nodeIds: z.array(z.string()).describe('Array of group node IDs to ungroup') }, async (args) => handleUngroupNodes(bridge, args) ); // ============================================================ // Creation Tools // ============================================================ // figma_create_frame - Create a new frame server.tool( 'figma_create_frame', 'Create a frame.', { x: z.number().optional().default(0).describe('X position'), y: z.number().optional().default(0).describe('Y position'), width: z.number().optional().default(100).describe('Width in pixels'), height: z.number().optional().default(100).describe('Height in pixels'), name: z.string().optional().default('Frame').describe('Frame name'), fills: colorSchema.optional().describe('Fill color'), parentId: z.string().optional().describe('Parent node ID (defaults to current page)') }, async (args) => handleCreateFrame(bridge, args) ); // figma_create_text - Create a new text node server.tool( 'figma_create_text', 'Create a text node.', { x: z.number().optional().default(0).describe('X position'), y: z.number().optional().default(0).describe('Y position'), text: z.string().optional().default('Text').describe('The text content'), fontSize: z.number().optional().default(16).describe('Font size in pixels'), fontFamily: z.string().optional().default('Inter').describe('Font family name'), fontStyle: z.string().optional().default('Regular').describe('Font style (Regular, Bold, etc.)'), fills: colorSchema.optional().describe('Text color'), name: z.string().optional().default('Text').describe('Node name'), parentId: z.string().optional().describe('Parent node ID (defaults to current page)') }, async (args) => handleCreateText(bridge, args) ); // ============================================================ // Navigation Tools // ============================================================ // figma_set_selection - Set the current selection server.tool( 'figma_set_selection', 'Set selection. Empty array clears.', { nodeIds: z.array(z.string()).describe('Array of node IDs to select (empty array to clear)') }, async (args) => handleSetSelection(bridge, args) ); // figma_set_current_page - Switch to a different page server.tool( 'figma_set_current_page', 'Switch to a different page in the Figma document.', { pageId: z.string().describe('The page ID to switch to') }, async (args) => handleSetCurrentPage(bridge, args) ); // ============================================================ // Export Tools // ============================================================ // figma_export_node - Export a node as an image server.tool( 'figma_export_node', 'Export a node as an image (PNG, SVG, JPG, or PDF). Returns base64-encoded data.', { nodeId: z.string().describe('The node ID to export'), format: z.enum(['PNG', 'SVG', 'JPG', 'PDF']).optional().default('PNG').describe('Export format'), scale: z.number().optional().default(1).describe('Export scale (1 = 100%, 2 = 200%, etc.)') }, async (args) => handleExportNode(bridge, args) ); // ============================================================ // Phase 2 Tools // ============================================================ // figma_create_ellipse - Create an ellipse/circle server.tool( 'figma_create_ellipse', 'Create ellipse. Use arcData for arcs/rings.', { x: z.number().optional().default(0).describe('X position'), y: z.number().optional().default(0).describe('Y position'), width: z.number().optional().default(100).describe('Width in pixels (diameter for circle)'), height: z.number().optional().default(100).describe('Height in pixels (same as width for circle)'), name: z.string().optional().default('Ellipse').describe('Node name'), fills: colorSchema.optional().describe('Fill color'), parentId: z.string().optional().describe('Parent node ID (defaults to current page)'), arcData: z.object({ startingAngle: z.number().min(0).max(6.28319).optional().describe('Starting angle in radians (0 = 3 o\'clock)'), endingAngle: z.number().min(0).max(6.28319).optional().describe('Ending angle in radians (2*PI = full circle)'), innerRadius: z.number().min(0).max(1).optional().describe('Inner radius ratio (0 = solid, 0.5 = 50% hole)') }).optional().describe('Arc data for partial ellipses or rings') }, async (args) => handleCreateEllipse(bridge, args) ); // figma_set_effects - Set effects (shadows, blurs) server.tool( 'figma_set_effects', 'Set effects. Replaces existing.', { nodeId: z.string().describe('The node ID to modify'), effects: z.array(z.union([ z.object({ type: z.enum(['DROP_SHADOW', 'INNER_SHADOW']).describe('Shadow type'), color: colorSchema.optional().describe('Shadow color'), offset: z.object({ x: z.number().describe('Horizontal offset'), y: z.number().describe('Vertical offset') }).optional().describe('Shadow offset'), radius: z.number().min(0).optional().describe('Blur radius'), spread: z.number().optional().describe('Spread radius'), visible: z.boolean().optional().describe('Whether effect is visible'), blendMode: z.string().optional().describe('Blend mode') }), z.object({ type: z.enum(['LAYER_BLUR', 'BACKGROUND_BLUR']).describe('Blur type'), radius: z.number().min(0).describe('Blur radius'), visible: z.boolean().optional().describe('Whether effect is visible') }) ])).describe('Array of effects to apply') }, async (args) => handleSetEffects(bridge, args) ); // figma_set_auto_layout - Configure auto-layout server.tool( 'figma_set_auto_layout', 'Configure auto-layout on a frame. Enables responsive layouts with automatic spacing and alignment.', { nodeId: z.string().describe('The frame node ID to configure'), layoutMode: z.enum(['NONE', 'HORIZONTAL', 'VERTICAL']).optional().describe('Layout direction: NONE (disable), HORIZONTAL (row), or VERTICAL (column)'), primaryAxisSizingMode: z.enum(['FIXED', 'AUTO']).optional().describe('How the frame sizes along the primary axis'), counterAxisSizingMode: z.enum(['FIXED', 'AUTO']).optional().describe('How the frame sizes along the counter axis'), primaryAxisAlignItems: z.enum(['MIN', 'CENTER', 'MAX', 'SPACE_BETWEEN']).optional().describe('Alignment of children along primary axis'), counterAxisAlignItems: z.enum(['MIN', 'CENTER', 'MAX', 'BASELINE']).optional().describe('Alignment of children along counter axis'), paddingTop: z.number().min(0).optional().describe('Top padding in pixels'), paddingRight: z.number().min(0).optional().describe('Right padding in pixels'), paddingBottom: z.number().min(0).optional().describe('Bottom padding in pixels'), paddingLeft: z.number().min(0).optional().describe('Left padding in pixels'), itemSpacing: z.number().min(0).optional().describe('Space between items in pixels'), counterAxisSpacing: z.number().min(0).optional().describe('Space between rows when wrapped'), layoutWrap: z.enum(['NO_WRAP', 'WRAP']).optional().describe('Whether to wrap items to new rows/columns') }, async (args) => handleSetAutoLayout(bridge, args) ); // figma_get_local_styles - List local styles server.tool( 'figma_get_local_styles', 'List all local styles defined in the document (colors, text, effects, grids). TIP: Use figma_search_styles instead when looking for specific styles by name - it returns compact results and reduces token usage.', { type: z.enum(['PAINT', 'TEXT', 'EFFECT', 'GRID', 'ALL']).optional().default('ALL').describe('Filter by style type') }, async (args) => handleGetLocalStyles(bridge, args) ); // figma_apply_style - Apply a style to a node server.tool( 'figma_apply_style', 'Apply a local style to a node. Styles provide consistent, reusable design tokens.', { nodeId: z.string().describe('The node ID to apply the style to'), styleId: z.string().describe('The style ID to apply'), property: z.enum(['fills', 'strokes', 'text', 'effects', 'grid']).describe('Which property to apply the style to') }, async (args) => handleApplyStyle(bridge, args) ); // figma_create_component - Create a component server.tool( 'figma_create_component', 'Create a component.', { fromNodeId: z.string().optional().describe('Convert an existing node to a component'), x: z.number().optional().default(0).describe('X position'), y: z.number().optional().default(0).describe('Y position'), width: z.number().optional().default(100).describe('Width in pixels'), height: z.number().optional().default(100).describe('Height in pixels'), name: z.string().optional().default('Component').describe('Component name'), fills: colorSchema.optional().describe('Fill color'), parentId: z.string().optional().describe('Parent node ID (defaults to current page)'), description: z.string().optional().describe('Component description') }, async (args) => handleCreateComponent(bridge, args) ); // figma_create_instance - Create an instance of a component server.tool( 'figma_create_instance', 'Create a component instance.', { componentId: z.string().describe('The component ID to create an instance of'), x: z.number().optional().default(0).describe('X position'), y: z.number().optional().default(0).describe('Y position'), parentId: z.string().optional().describe('Parent node ID (defaults to current page)'), name: z.string().optional().describe('Instance name (defaults to component name)') }, async (args) => handleCreateInstance(bridge, args) ); // ============================================================ // Phase 3 Tools: Variables, Lines, Constraints // ============================================================ // figma_get_local_variables - Get local variables // WARNING: Can return 25k+ tokens for large design systems. Prefer figma_search_variables when possible. server.tool( 'figma_get_local_variables', 'Get all local variables and variable collections from the Figma document. Returns variables with their types (COLOR, FLOAT, STRING, BOOLEAN), modes, and values. WARNING: Can return 25k+ tokens and may be truncated. Use figma_search_variables instead when looking for specific variables.', { type: z.enum(['COLOR', 'FLOAT', 'STRING', 'BOOLEAN', 'ALL']).optional().default('ALL').describe('Filter by variable type') }, async (args) => handleGetLocalVariables(bridge, args) ); // figma_search_variables - Search variables with filtering (optimized for reduced token usage) // PREFERRED: Use this instead of figma_get_local_variables for ~50x token reduction server.tool( 'figma_search_variables', 'Search for variables by name pattern. More efficient than get_local_variables - use this when looking for specific variables like "tailwind/orange/*" or "*primary*". Returns compact results to reduce token usage. PREFERRED over figma_get_local_variables for efficiency (~500 tokens vs 25k+).', { namePattern: z.string().optional().describe('Filter by name pattern with wildcards. Examples: "tailwind/orange/*", "*primary*", "spacing/*". Use * for any characters.'), nameContains: z.string().optional().describe('Simple filter: find variables where name contains this string (case-insensitive). Example: "orange" matches "tailwind/orange/500"'), type: z.enum(['COLOR', 'FLOAT', 'STRING', 'BOOLEAN', 'ALL']).optional().default('ALL').describe('Filter by variable type'), collectionName: z.string().optional().describe('Filter by collection name (exact match or partial)'), compact: z.boolean().optional().default(true).describe('Return minimal data (id, name, hex/value only). Set false for full metadata.'), limit: z.number().optional().default(50).describe('Maximum number of variables to return') }, async (args) => handleSearchVariables(bridge, args) ); // ============================================================ // Smart Query Tools (token-efficient search) // ============================================================ // figma_search_nodes - Search nodes by name within a scope server.tool( 'figma_search_nodes', 'Search for nodes by name within a scope. PREFERRED for finding specific frames, sections, or elements. Requires parentId to scope search. Returns compact results (~50 tokens/node vs ~500 for full).', { parentId: z.string().describe('Scope to search (page/frame/section ID). REQUIRED to prevent runaway queries.'), nameContains: z.string().optional().describe('Case-insensitive substring match. Example: "color scale" matches "Color Scale Section"'), namePattern: z.string().optional().describe('Glob pattern with wildcards. Examples: "*button*", "Header/*"'), types: z.array(z.string()).optional().describe('Filter by node types: FRAME, TEXT, SECTION, COMPONENT, INSTANCE, GROUP, etc.'), maxDepth: z.number().optional().default(-1).describe('How deep to search (-1 = unlimited, 1 = immediate children only)'), compact: z.boolean().optional().default(true).describe('Return minimal data (id, name, type, parentId, childCount)'), limit: z.number().optional().default(50).describe('Maximum number of results') }, async (args) => handleSearchNodes(bridge, args) ); // figma_search_components - Search local components by name server.tool( 'figma_search_components', 'Search local components by name. Use when looking for specific components like "Button", "Header", etc. Returns compact results with component metadata.', { nameContains: z.string().optional().describe('Case-insensitive substring match'), namePattern: z.string().optional().describe('Glob pattern with wildcards'), includeVariants: z.boolean().optional().default(false).describe('Include individual variants from component sets'), compact: z.boolean().optional().default(true).describe('Return minimal data'), limit: z.number().optional().default(50).describe('Maximum number of results') }, async (args) => handleSearchComponents(bridge, args) ); // figma_search_styles - Search local styles by name server.tool( 'figma_search_styles', 'Search local styles by name. More efficient than figma_get_local_styles when looking for specific styles.', { nameContains: z.string().optional().describe('Case-insensitive substring match'), type: z.enum(['PAINT', 'TEXT', 'EFFECT', 'GRID', 'ALL']).optional().default('ALL').describe('Filter by style type'), compact: z.boolean().optional().default(true).describe('Return minimal data'), limit: z.number().optional().default(50).describe('Maximum number of results') }, async (args) => handleSearchStyles(bridge, args) ); // figma_get_children - Get immediate children of a node server.tool( 'figma_get_children', 'Get immediate children of a node. Use for browsing hierarchy one level at a time. More efficient than figma_get_nodes for exploring structure.', { parentId: z.string().describe('Node ID to get children of. REQUIRED.'), compact: z.boolean().optional().default(true).describe('Return minimal data') }, async (args) => handleGetChildren(bridge, args) ); // figma_set_variable - Set variable value or bind to node server.tool( 'figma_set_variable', 'Set the value of an existing variable for a specific mode, or bind a variable to a node property.', { variableId: z.string().describe('The variable ID to set or bind'), modeId: z.string().optional().describe('Mode ID to set value for (required when setting value)'), value: z.union([ z.number(), z.string(), z.boolean(), z.object({ r: z.number().min(0).max(1).describe('Red (0-1)'), g: z.number().min(0).max(1).describe('Green (0-1)'), b: z.number().min(0).max(1).describe('Blue (0-1)'), a: z.number().min(0).max(1).optional().describe('Alpha (0-1)') }) ]).optional().describe('The value to set (number, string, boolean, or color object)'), nodeId: z.string().optional().describe('Node ID to bind variable to (for binding operation)'), field: z.string().optional().describe('Node field to bind to (e.g., "opacity", "cornerRadius", "fills", "strokes")'), paintIndex: z.number().optional().default(0).describe('Paint array index when binding to fills or strokes') }, async (args) => handleSetVariable(bridge, args) ); // figma_create_line - Create a line server.tool( 'figma_create_line', 'Create a line.', { x: z.number().optional().default(0).describe('X position'), y: z.number().optional().default(0).describe('Y position'), length: z.number().optional().default(100).describe('Line length in pixels'), rotation: z.number().optional().default(0).describe('Line rotation in degrees (0 = horizontal)'), name: z.string().optional().default('Line').describe('Node name'), strokeWeight: z.number().optional().default(1).describe('Stroke weight in pixels'), strokes: colorSchema.optional().describe('Stroke color'), strokeCap: z.enum(['NONE', 'ROUND', 'SQUARE', 'ARROW_LINES', 'ARROW_EQUILATERAL']).optional().default('NONE').describe('Stroke cap style (ARROW_LINES/ARROW_EQUILATERAL for arrows)'), parentId: z.string().optional().describe('Parent node ID (defaults to current page)') }, async (args) => handleCreateLine(bridge, args) ); // figma_set_constraints - Set resize constraints server.tool( 'figma_set_constraints', 'Set resize constraints on a node. Constraints control how a node resizes when its parent frame resizes. Only works on nodes inside frames (not auto-layout frames).', { nodeId: z.string().describe('The node ID to set constraints on'), horizontal: z.enum(['MIN', 'CENTER', 'MAX', 'STRETCH', 'SCALE']).optional().describe('Horizontal constraint: MIN (left), CENTER, MAX (right), STRETCH (left+right), SCALE (proportional)'), vertical: z.enum(['MIN', 'CENTER', 'MAX', 'STRETCH', 'SCALE']).optional().describe('Vertical constraint: MIN (top), CENTER, MAX (bottom), STRETCH (top+bottom), SCALE (proportional)') }, async (args) => handleSetConstraints(bridge, args) ); // ============================================================ // Phase 4 Tools: Polygons, Boolean Operations, Viewport, Blend Mode, Detach // ============================================================ // DISABLED - Uncomment to enable advanced shape tools // // figma_create_polygon - Create a polygon or star // server.tool( // 'figma_create_polygon', // 'Create a polygon (triangle, pentagon, hexagon, etc.) or star shape. Set innerRadius (0-1) to create a star with spiky points.', // { // x: z.number().optional().default(0).describe('X position'), // y: z.number().optional().default(0).describe('Y position'), // width: z.number().optional().default(100).describe('Width in pixels'), // height: z.number().optional().default(100).describe('Height in pixels'), // pointCount: z.number().min(3).optional().default(5).describe('Number of sides (polygon) or points (star). Minimum 3.'), // innerRadius: z.number().min(0).max(1).optional().describe('Inner radius ratio for stars (0-1). 0 = very spiky, 1 = polygon. Omit for regular polygon.'), // name: z.string().optional().describe('Node name'), // fills: colorSchema.optional().describe('Fill color'), // strokes: colorSchema.optional().describe('Stroke color'), // strokeWeight: z.number().optional().describe('Stroke weight in pixels'), // cornerRadius: z.number().optional().describe('Corner radius for vertices'), // parentId: z.string().optional().describe('Parent node ID (defaults to current page)') // }, // async (args) => handleCreatePolygon(bridge, args) // ); // // figma_boolean_operation - Perform boolean operations on shapes // server.tool( // 'figma_boolean_operation', // 'Combine multiple shapes using boolean operations (union, subtract, intersect, exclude) or flatten them into a single vector.', // { // operation: z.enum(['UNION', 'SUBTRACT', 'INTERSECT', 'EXCLUDE', 'FLATTEN']).describe('Boolean operation type: UNION (combine), SUBTRACT (cut), INTERSECT (overlap only), EXCLUDE (non-overlap only), FLATTEN (destructive vector)'), // nodeIds: z.array(z.string()).min(2).describe('Array of node IDs to combine (minimum 2 nodes)'), // name: z.string().optional().describe('Name for the resulting node') // }, // async (args) => handleBooleanOperation(bridge, args) // ); // // figma_zoom_to_node - Zoom viewport to focus on specific nodes // server.tool( // 'figma_zoom_to_node', // 'Scroll and zoom the Figma viewport to focus on specific nodes. Automatically calculates zoom level to fit all specified nodes.', // { // nodeIds: z.array(z.string()).min(1).describe('Array of node IDs to zoom to') // }, // async (args) => handleZoomToNode(bridge, args) // ); // // figma_set_blend_mode - Set blend mode on a node // server.tool( // 'figma_set_blend_mode', // 'Set the blend mode (layer blending) of a node. Controls how the node visually blends with layers below it.', // { // nodeId: z.string().describe('The node ID to modify'), // blendMode: z.enum([ // 'PASS_THROUGH', 'NORMAL', 'DARKEN', 'MULTIPLY', 'LINEAR_BURN', 'COLOR_BURN', // 'LIGHTEN', 'SCREEN', 'LINEAR_DODGE', 'COLOR_DODGE', 'OVERLAY', 'SOFT_LIGHT', // 'HARD_LIGHT', 'DIFFERENCE', 'EXCLUSION', 'HUE', 'SATURATION', 'COLOR', 'LUMINOSITY' // ]).describe('Blend mode: NORMAL (default), MULTIPLY (darken), SCREEN (lighten), OVERLAY (contrast), etc.') // }, // async (args) => handleSetBlendMode(bridge, args) // ); // figma_detach_instance - Detach instance from component server.tool( 'figma_detach_instance', 'Detach a component instance, converting it to a regular frame. Preserves overrides but severs the link to the main component.', { nodeId: z.string().describe('The instance node ID to detach') }, async (args) => handleDetachInstance(bridge, args) ); // ============================================================ // Phase 5 Tools: Layout Align, Vector, Rename, Reorder // ============================================================ // figma_set_layout_align - Set layout alignment for auto-layout children server.tool( 'figma_set_layout_align', 'Set how a child behaves within an auto-layout frame. Controls individual alignment (STRETCH), growth (fill container), and absolute positioning.', { nodeId: z.string().describe('The child node ID to modify'), layoutAlign: z.enum(['MIN', 'CENTER', 'MAX', 'STRETCH', 'INHERIT']).optional().describe('Counter-axis alignment: STRETCH to fill width/height'), layoutGrow: z.number().min(0).max(1).optional().describe('Primary-axis growth: 0 = fixed size, 1 = fill available space'), layoutPositioning: z.enum(['AUTO', 'ABSOLUTE']).optional().describe('AUTO = follow auto-layout, ABSOLUTE = manually positioned') }, async (args) => handleSetLayoutAlign(bridge, args) ); // DISABLED - Uncomment to enable custom vector paths // // figma_create_vector - Create a custom vector path // server.tool( // 'figma_create_vector', // 'Create a custom vector shape using SVG-style path data. Supports M (move), L (line), Q (quadratic curve), C (cubic bezier), Z (close).', // { // x: z.number().optional().default(0).describe('X position'), // y: z.number().optional().default(0).describe('Y position'), // data: z.string().describe('SVG path string (e.g., "M 0 100 L 100 100 L 50 0 Z" for triangle)'), // windingRule: z.enum(['NONZERO', 'EVENODD', 'NONE']).optional().default('NONZERO').describe('Fill rule: NONZERO (solid), EVENODD (holes), NONE (outline only)'), // name: z.string().optional().default('Vector').describe('Node name'), // fills: colorSchema.optional().describe('Fill color'), // strokes: colorSchema.optional().describe('Stroke color'), // strokeWeight: z.number().optional().describe('Stroke weight in pixels'), // parentId: z.string().optional().describe('Parent node ID (defaults to current page)') // }, // async (args) => handleCreateVector(bridge, args) // ); // figma_rename_node - Rename nodes server.tool( 'figma_rename_node', 'Rename one or more nodes. For batch renaming, all nodes get the same name.', { nodeId: z.string().optional().describe('Single node ID to rename'), nodeIds: z.array(z.string()).optional().describe('Array of node IDs to rename (batch)'), name: z.string().describe('The new name for the node(s)') }, async (args) => handleRenameNode(bridge, args) ); // figma_reorder_node - Change z-order of a node server.tool( 'figma_reorder_node', 'Change the z-order (layer order) of a node. Bring to front, send to back, or move to a specific index.', { nodeId: z.string().describe('The node ID to reorder'), position: z.union([ z.literal('front'), z.literal('back'), z.number() ]).describe('Position: "front" (top), "back" (bottom), or index number') }, async (args) => handleReorderNode(bridge, args) ); // ============================================================ // Design System Creation Tools // ============================================================ // figma_set_text_style - Set font properties on existing text server.tool( 'figma_set_text_style', 'Set text font properties.', { nodeId: z.string().describe('Text node ID'), fontSize: z.number().optional().describe('Font size in pixels'), fontFamily: z.string().optional().describe('Font family (e.g., "Inter")'), fontStyle: z.string().optional().describe('Font style (e.g., "Bold", "Regular")'), textCase: z.enum(['ORIGINAL', 'UPPER', 'LOWER', 'TITLE']).optional().describe('Text case transformation'), textDecoration: z.enum(['NONE', 'UNDERLINE', 'STRIKETHROUGH']).optional().describe('Text decoration'), lineHeight: z.union([ z.object({ unit: z.literal('AUTO') }), z.object({ unit: z.literal('PIXELS'), value: z.number() }), z.object({ unit: z.literal('PERCENT'), value: z.number() }) ]).optional().describe('Line height (AUTO, or PIXELS/PERCENT with value)'), letterSpacing: z.union([ z.object({ unit: z.literal('PIXELS'), value: z.number() }), z.object({ unit: z.literal('PERCENT'), value: z.number() }) ]).optional().describe('Letter spacing (PIXELS or PERCENT with value)'), textAlignHorizontal: z.enum(['LEFT', 'CENTER', 'RIGHT', 'JUSTIFIED']).optional().describe('Horizontal text alignment'), textAlignVertical: z.enum(['TOP', 'CENTER', 'BOTTOM']).optional().describe('Vertical text alignment') }, async (args) => handleSetTextStyle(bridge, args) ); // figma_create_paint_style - Create a local paint style server.tool( 'figma_create_paint_style', 'Create a paint style.', { name: z.string().describe('Style name (use "/" for folders, e.g., "Brand/Primary")'), fills: colorSchema.describe('Fill color - use { color: "#RRGGBB" } for simple colors'), description: z.string().optional().describe('Style description') }, async (args) => handleCreatePaintStyle(bridge, args) ); // figma_create_text_style - Create a local text style server.tool( 'figma_create_text_style', 'Create a text style.', { name: z.string().describe('Style name (use "/" for folders)'), fontFamily: z.string().optional().default('Inter').describe('Font family'), fontStyle: z.string().optional().default('Regular').describe('Font style (Regular, Bold, etc.)'), fontSize: z.number().optional().default(16).describe('Font size in pixels'), lineHeight: z.union([ z.object({ unit: z.literal('AUTO') }), z.object({ unit: z.literal('PIXELS'), value: z.number() }), z.object({ unit: z.literal('PERCENT'), value: z.number() }) ]).optional().describe('Line height'), letterSpacing: z.union([ z.object({ unit: z.literal('PIXELS'), value: z.number() }), z.object({ unit: z.literal('PERCENT'), value: z.number() }) ]).optional().describe('Letter spacing'), textCase: z.enum(['ORIGINAL', 'UPPER', 'LOWER', 'TITLE']).optional().describe('Text case'), textDecoration: z.enum(['NONE', 'UNDERLINE', 'STRIKETHROUGH']).optional().describe('Text decoration'), description: z.string().optional().describe('Style description') }, async (args) => handleCreateTextStyle(bridge, args) ); // figma_create_variable_collection - Create a variable collection server.tool( 'figma_create_variable_collection', 'Create a new variable collection to organize variables.', { name: z.string().describe('Collection name'), modes: z.array(z.string()).optional().describe('Mode names (defaults to ["Mode 1"])') }, async (args) => handleCreateVariableCollection(bridge, args) ); // figma_create_variable - Create a variable server.tool( 'figma_create_variable', 'Create a new variable in a collection.', { collectionId: z.string().describe('Variable collection ID'), name: z.string().describe('Variable name (use "/" for groups, e.g., "colors/primary")'), type: z.enum(['COLOR', 'FLOAT', 'STRING', 'BOOLEAN']).describe('Variable type'), value: z.union([ z.string(), z.number(), z.boolean(), z.object({ r: z.number(), g: z.number(), b: z.number(), a: z.number().optional() }), z.object({ color: z.string() }) ]).optional().describe('Initial value for default mode'), aliasOf: z.string().optional().describe('Variable ID to alias (instead of direct value)'), description: z.string().optional().describe('Variable description'), scopes: z.array(z.enum([ 'ALL_SCOPES', 'TEXT_CONTENT', 'CORNER_RADIUS', 'WIDTH_HEIGHT', 'GAP', 'ALL_FILLS', 'FRAME_FILL', 'SHAPE_FILL', 'TEXT_FILL', 'STROKE_COLOR', 'STROKE_FLOAT', 'EFFECT_FLOAT', 'EFFECT_COLOR', 'OPACITY', 'FONT_FAMILY', 'FONT_STYLE', 'FONT_WEIGHT', 'FONT_SIZE', 'LINE_HEIGHT', 'LETTER_SPACING', 'PARAGRAPH_SPACING', 'PARAGRAPH_INDENT' ])).optional().describe('Where this variable can be used') }, async (args) => handleCreateVariable(bridge, args) ); // figma_rename_variable - Rename an existing variable server.tool( 'figma_rename_variable', 'Rename an existing variable. Use "/" in the name to organize into groups (e.g., "font weight/heading/h1").', { variableId: z.string().describe('The variable ID to rename'), name: z.string().describe('The new name for the variable (use "/" for groups)') }, async (args) => handleRenameVariable(bridge, args) ); // figma_delete_variables - Delete one or more variables server.tool( 'figma_delete_variables', 'Delete one or more variables from the document. Use with caution - this cannot be undone.', { variableIds: z.array(z.string()).describe('Array of variable IDs to delete') }, async (args) => handleDeleteVariables(bridge, args) ); // figma_delete_variable_collection - Delete a variable collection server.tool( 'figma_delete_variable_collection', 'Delete a variable collection and all its variables. Use with caution - this cannot be undone.', { collectionId: z.string().describe('The collection ID to delete') }, async (args) => handleDeleteVariableCollection(bridge, args) ); // figma_rename_variable_collection - Rename a variable collection server.tool( 'figma_rename_variable_collection', 'Rename a variable collection.', { collectionId: z.string().describe('The collection ID to rename'), name: z.string().describe('The new name for the collection') }, async (args) => handleRenameVariableCollection(bridge, args) ); // figma_rename_mode - Rename a mode in a collection server.tool( 'figma_rename_mode', 'Rename a mode in a variable collection (e.g., "Mode 1" to "dark").', { collectionId: z.string().describe('The collection ID containing the mode'), modeId: z.string().describe('The mode ID to rename'), name: z.string().describe('The new name for the mode') }, async (args) => handleRenameMode(bridge, args) ); // figma_add_mode - Add a mode to a collection server.tool( 'figma_add_mode', 'Add a new mode to a variable collection.', { collectionId: z.string().describe('The collection ID to add mode to'), name: z.string().describe('Name for the new mode') }, async (args) => handleAddMode(bridge, args) ); // figma_delete_mode - Delete a mode from a collection server.tool( 'figma_delete_mode', 'Delete a mode from a variable collection. Cannot delete the last mode.', { collectionId: z.string().describe('The collection ID containing the mode'), modeId: z.string().describe('The mode ID to delete') }, async (args) => handleDeleteMode(bridge, args) ); // figma_unbind_variable - Remove variable binding from a node server.tool( 'figma_unbind_variable', 'Remove a variable binding from a node property.', { nodeId: z.string().describe('The node ID to unbind from'), field: z.string().describe('The field to unbind (fills, strokes, opacity, cornerRadius, etc.)'), paintIndex: z.number().optional().default(0).describe('Paint array index for fills/strokes') }, async (args) => handleUnbindVariable(bridge, args) ); // ============================================================ // Page Management Tools // ============================================================ // figma_create_page - Create a new page server.tool( 'figma_create_page', 'Create a new page in the Figma document. Returns the created page.', { name: z.string().describe('Name for the new page'), index: z.number().optional().describe('Position in the page list (0 = first). Defaults to end.') }, async (args) => handleCreatePage(bridge, args) ); // figma_rename_page - Rename a page server.tool( 'figma_rename_page', 'Rename an existing page in the Figma document.', { pageId: z.string().describe('The page ID to rename'), name: z.string().describe('The new name for the page') }, async (args) => handleRenamePage(bridge, args) ); // figma_delete_page - Delete a page server.tool( 'figma_delete_page', 'Delete a page from the Figma document. Cannot delete the last remaining page.', { pageId: z.string().describe('The page ID to delete') }, async (args) => handleDeletePage(bridge, args) ); // DISABLED - Uncomment to enable page reordering // // figma_reorder_page - Reorder a page // server.tool( // 'figma_reorder_page', // 'Change the position of a page in the page list.', // { // pageId: z.string().describe('The page ID to reorder'), // index: z.number().describe('New position in the page list (0 = first)') // }, // async (args) => handleReorderPage(bridge, args) // ); // ============================================================ // Node Structure Tools // ============================================================ // figma_reparent_nodes - Move nodes to a different parent server.tool( 'figma_reparent_nodes', 'Move nodes to a different parent container. Useful for reorganizing the layer hierarchy.', { nodeIds: z.array(z.string()).describe('Array of node IDs to move'), newParentId: z.string().describe('The new parent node ID (must be a frame, group, or page)'), index: z.number().optional().describe('Position within the new parent (0 = bottom/back). Defaults to top/front.') }, async (args) => handleReparentNodes(bridge, args) ); // figma_move_to_page - Move nodes to a different page server.tool( 'figma_move_to_page', 'Move nodes from their current page to a different page.', { nodeIds: z.array(z.string()).describe('Array of node IDs to move'), targetPageId: z.string().describe('The destination page ID'), x: z.number().optional().describe('X position on the target page'), y: z.number().optional().describe('Y position on the target page') }, async (args) => handleMoveToPage(bridge, args) ); // ============================================================ // Component Instance Tools // ============================================================ // figma_swap_instance - Swap instance to different component server.tool( 'figma_swap_instance', 'Swap a component instance to use a different component. Preserves position and size.', { instanceId: z.string().describe('The instance node ID to swap'), newComponentId: z.string().describe('The component ID to swap to') }, async (args) => handleSwapInstance(bridge, args) ); // ============================================================ // Additional Tools // ============================================================ // figma_duplicate_page - Clone an entire page server.tool( 'figma_duplicate_page', 'Clone an entire page including all its contents. The new page is inserted after the original.', { pageId: z.string().describe('The page ID to duplicate'), name: z.string().optional().describe('Name for the new page (defaults to "original name + copy")') }, async (args) => handleDuplicatePage(bridge, args) ); // figma_set_rotation - Set rotation on nodes server.tool( 'figma_set_rotation', 'Set the rotation (in degrees) of one or more nodes. Rotation is around the center point.', { nodeIds: z.array(z.string()).describe('Array of node IDs to rotate'), rotation: z.number().min(-180).max(180).describe('Rotation in degrees (-180 to 180)') }, async (args) => handleSetRotation(bridge, args) ); // figma_combine_as_variants - Combine components into a component set server.tool( 'figma_combine_as_variants', 'Combine multiple components into a component set with variants. Components must use variant naming (e.g., "Size=Large", "State=Active"). Returns the new component set.', { componentIds: z.array(z.string()).min(2).describe('Array of component IDs to combine (minimum 2)') }, async (args) => handleCombineAsVariants(bridge, args) ); // DISABLED - Uncomment to enable layout grids // // figma_set_layout_grids - Set layout grids on a frame // server.tool( // 'figma_set_layout_grids', // 'Set layout grids on a frame. Grids help with alignment and spacing. Pass an empty array to remove all grids.', // { // nodeId: z.string().describe('The frame node ID to set grids on'), // layoutGrids: z.array(z.object({ // pattern: z.enum(['COLUMNS', 'ROWS', 'GRID']).describe('Grid pattern type'), // sectionSize: z.number().optional().describe('Size of each column/row/cell in pixels'), // visible: z.boolean().optional().default(true).describe('Whether grid is visible'), // color: z.object({ // r: z.number().min(0).max(1).describe('Red (0-1)'), // g: z.number().min(0).max(1).describe('Green (0-1)'), // b: z.number().min(0).max(1).describe('Blue (0-1)'), // a: z.number().min(0).max(1).optional().default(0.1).describe('Alpha (0-1)') // }).optional().describe('Grid color with alpha'), // alignment: z.enum(['MIN', 'CENTER', 'MAX', 'STRETCH']).optional().describe('Column/row alignment (for COLUMNS/ROWS pattern)'), // gutterSize: z.number().optional().describe('Gutter size between columns/rows in pixels'), // offset: z.number().optional().describe('Offset from edge in pixels'), // count: z.number().optional().describe('Number of columns/rows (use large number like 100 for auto)') // })).describe('Array of layout grid configurations') // }, // async (args) => handleSetLayoutGrids(bridge, args) // ); }

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/magic-spells/figma-mcp-bridge'

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