Skip to main content
Glama
ArchimedesCrypto

Figma MCP Server with Chunking

get_file_nodes

Retrieve specific nodes from a Figma file using node IDs, enabling efficient access to design elements even in large files managed by the Figma MCP Server with Chunking.

Instructions

Get specific nodes from a Figma file

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
file_keyYesFigma file key
idsYesArray of node IDs to retrieve

Implementation Reference

  • Core handler implementation for fetching specific Figma file nodes. Handles API calls, single/multiple node logic, child pagination (cursor/pageSize for single nodes), depth traversal, node type filtering, property exclusion, and summarization to manage response size.
    async getFileNodes(fileKey: string, ids: string[], options: { pageSize?: number; maxResponseSize?: number; cursor?: string; depth?: number; nodeTypes?: string[]; excludeProps?: string[]; summarizeNodes?: boolean; } = {}) { try { // Input validation if (!fileKey || typeof fileKey !== 'string' || fileKey.trim() === '') { throw new Error('fileKey must be a non-empty string'); } if (!ids || !Array.isArray(ids) || ids.length === 0) { throw new Error('ids must be a non-empty array'); } // Basic ID validation - Figma API will handle the rest for (const id of ids) { if (!id || typeof id !== 'string') { throw new Error('All node IDs must be non-empty strings'); } } if (process.env.MCP_DEBUG) { console.debug('[MCP Debug] Getting nodes for file:', fileKey, 'Options:', options); } const { pageSize = 50, maxResponseSize = 50, cursor, depth = 2, nodeTypes, excludeProps, summarizeNodes = false } = options; // Validate options if (pageSize !== undefined) { if (typeof pageSize !== 'number' || pageSize < 1 || pageSize > 1000) { throw new Error('pageSize must be between 1 and 1000'); } } if (depth !== undefined) { if (typeof depth !== 'number' || depth < 0 || depth > 10) { throw new Error('depth must be between 0 and 10'); } } if (maxResponseSize !== undefined) { if (typeof maxResponseSize !== 'number' || maxResponseSize < 1 || maxResponseSize > 100) { throw new Error('maxResponseSize must be between 1 and 100 MB'); } } if (excludeProps && excludeProps.length > 0) { const criticalProps = ['id', 'type', 'name']; for (const prop of excludeProps) { if (criticalProps.includes(prop)) { throw new Error(`Cannot exclude critical property: ${prop}`); } } } // For single node requests, implement child pagination if (ids.length === 1) { const nodeId = ids[0]; // Step 1: Fetch the parent node with depth=1 to get immediate children list // We always fetch parent with depth=1 first for pagination to work let shallowResponse; try { shallowResponse = await this.client.get(`/files/${fileKey}/nodes`, { params: { ids: nodeId, depth: 1 // Shallow fetch to get list of immediate children IDs } }); } catch (error: any) { if (error.response?.status === 413 || error.response?.status === 504) { throw new Error( `Node is too large to fetch all at once. ` + `Try these parameters to paginate through its children:\n` + `- pageSize: 10 (fetch 10 children at a time)\n` + `- cursor: "0" (start from first child)\n` + `- depth: 1 (immediate children only)\n` + `- summarizeNodes: true (minimal data)\n` + `- excludeProps: ["fills", "effects", "strokes"] (remove heavy properties)` ); } throw error; } if (!shallowResponse.data || !shallowResponse.data.nodes) { throw new Error('Invalid response from Figma API'); } let mainNode = shallowResponse.data.nodes[nodeId]; if (!mainNode) { throw new Error('Requested node not found'); } // Handle Figma's document wrapper - sometimes nodes come wrapped in a 'document' object // Clone to avoid mutations const actualNode = { ...(mainNode.document || mainNode) }; // Step 2: Extract children IDs (safely) const allChildIds: string[] = []; if (actualNode.children) { if (!Array.isArray(actualNode.children)) { console.warn('[MCP Warning] Node children property is not an array'); } else { for (const child of actualNode.children) { if (child && typeof child === 'object' && child.id && typeof child.id === 'string') { allChildIds.push(child.id); } } } } if (process.env.MCP_DEBUG) { console.debug(`[MCP Debug] Found ${allChildIds.length} children`); } // Step 3: Paginate through children const startIndex = cursor ? parseInt(cursor, 10) : 0; if (isNaN(startIndex) || startIndex < 0) { throw new Error(`Invalid cursor value: ${cursor}. Must be a non-negative number.`); } if (startIndex >= allChildIds.length) { // Cursor is beyond the last child - return empty result with hasMore=false return { nodes: { [nodeId]: actualNode }, pagination: { totalChildren: allChildIds.length, fetchedChildren: 0, nextCursor: undefined, hasMore: false, message: allChildIds.length === 0 ? 'Node has no children' : `Cursor beyond range (max: ${allChildIds.length - 1})` } }; } const safePageSize = pageSize; // Already validated above const endIndex = Math.min(startIndex + safePageSize, allChildIds.length); const childIdsToFetch = allChildIds.slice(startIndex, endIndex); let childrenData: any = {}; // Step 4: Fetch the selected page of children (if any) if (childIdsToFetch.length > 0) { const childParams: any = { ids: childIdsToFetch.join(',') }; // Apply depth to children fetch (depth applies to how deep we fetch each child) // depth=1 means fetch children with their immediate children // depth=2 means fetch children with 2 levels of descendants, etc. if (typeof depth === 'number' && depth >= 0) { childParams.depth = depth; } let childResponse; try { childResponse = await this.client.get(`/files/${fileKey}/nodes`, { params: childParams, timeout: 30000 // 30 second timeout }); } catch (childError: any) { console.error('[MCP Error] Failed to fetch children:', childError.message); // Return partial result with parent only return { nodes: { [nodeId]: actualNode }, pagination: { totalChildren: allChildIds.length, fetchedChildren: 0, nextCursor: startIndex.toString(), hasMore: true, error: 'Failed to fetch children, returning parent only' } }; } if (!childResponse.data || !childResponse.data.nodes) { console.warn('[MCP Warning] Invalid response for child nodes, using empty object'); childrenData = {}; } else { childrenData = childResponse.data.nodes; } // Handle document wrapper for children too for (const [childId, childNode] of Object.entries(childrenData)) { if ((childNode as any).document) { childrenData[childId] = (childNode as any).document; } } } // Step 5: Combine parent and children nodes // Note: We don't modify the parent's children array - it shows all children IDs // The fetched children are separate entries in the nodes object let processedData: any = { [nodeId]: actualNode, // Parent node (already has full children array from depth=1 fetch) ...childrenData // Fetched children (based on pagination) }; // Filter by node types if specified // Note: Parent node is always kept regardless of its type (for context) if (nodeTypes && nodeTypes.length > 0) { const filteredData: any = { [nodeId]: processedData[nodeId] // Always keep parent for context }; for (const [id, node] of Object.entries(processedData)) { if (id !== nodeId) { const nodeType = (node as any).type; if (nodeType && nodeTypes.includes(nodeType)) { filteredData[id] = node; } } } processedData = filteredData; } // Exclude properties if specified if (excludeProps && excludeProps.length > 0) { const cleanedData: any = {}; for (const [id, node] of Object.entries(processedData)) { cleanedData[id] = this.nodeProcessor.filterNodeProperties(node as SceneNode, excludeProps); } processedData = cleanedData; } // Apply summarization if requested if (summarizeNodes) { const summarizedData: any = {}; for (const [id, node] of Object.entries(processedData)) { summarizedData[id] = this.nodeProcessor.summarizeNode(node as SceneNode); } processedData = summarizedData; } // Check response size (with safety for large objects) let responseSize = 0; try { responseSize = JSON.stringify(processedData).length / (1024 * 1024); // in MB if (responseSize > maxResponseSize) { console.warn(`[MCP Warning] Response size ${responseSize.toFixed(2)}MB exceeds max ${maxResponseSize}MB`); } } catch (e) { console.warn('[MCP Warning] Response too large to calculate size'); } return { nodes: processedData, pagination: { totalChildren: allChildIds.length, fetchedChildren: childIdsToFetch.length, nextCursor: endIndex < allChildIds.length ? endIndex.toString() : undefined, hasMore: endIndex < allChildIds.length } }; } // For multiple nodes, fetch them all at once (no child pagination) // Warn if pagination parameters are provided with multiple nodes if (cursor || (pageSize && pageSize !== 50)) { console.warn( '[MCP Warning] Pagination parameters (pageSize, cursor) are ignored when fetching multiple nodes. ' + 'Pagination only works when fetching a single node (to paginate through its children).' ); } const params: any = { ids: ids.join(',') }; if (typeof depth === 'number') params.depth = depth; const response = await this.client.get(`/files/${fileKey}/nodes`, { params, timeout: 30000 // 30 second timeout }); if (!response.data || !response.data.nodes) { throw new Error('Invalid response from Figma API'); } let nodeData = response.data.nodes; // Handle document wrapper for multiple nodes for (const [id, node] of Object.entries(nodeData)) { if ((node as any).document) { nodeData[id] = (node as any).document; } } // Apply filters and transformations if (nodeTypes && nodeTypes.length > 0) { const filteredNodes: any = {}; for (const [id, node] of Object.entries(nodeData)) { const nodeType = (node as any).type; if (nodeType && nodeTypes.includes(nodeType)) { filteredNodes[id] = node; } } nodeData = filteredNodes; } if (excludeProps && excludeProps.length > 0) { const processedNodes: any = {}; for (const [id, node] of Object.entries(nodeData)) { processedNodes[id] = this.nodeProcessor.filterNodeProperties(node as SceneNode, excludeProps); } nodeData = processedNodes; } if (summarizeNodes) { const summarizedNodes: any = {}; for (const [id, node] of Object.entries(nodeData)) { summarizedNodes[id] = this.nodeProcessor.summarizeNode(node as SceneNode); } nodeData = summarizedNodes; } // Return with clear indication that pagination doesn't apply const result: any = { nodes: nodeData }; // Only include pagination info if user tried to use pagination params if (cursor || (pageSize && pageSize !== 50)) { result.pagination = { warning: 'Pagination parameters were ignored. Pagination only works when fetching a single node.', explanation: 'To paginate: fetch one node at a time, use pageSize and cursor to navigate through its children.' }; } return result; } catch (error: any) { // Sanitize error messages const message = error.message || 'Unknown error'; const sanitized = message.substring(0, 500); // Limit error message length console.error('[MCP Error] Failed to get file nodes:', sanitized); throw new Error(`Failed to get nodes: ${sanitized}`); } }
  • src/index.ts:220-290 (registration)
    Tool registration in ListToolsRequestSchema handler, including name, description, and detailed inputSchema defining parameters like file_key, ids, pageSize, cursor, depth, etc.
    { name: 'get_file_nodes', description: 'Get specific nodes from a Figma file. IMPORTANT: Pagination (pageSize/cursor) ONLY works when fetching a SINGLE node - it paginates through that node\'s children. When fetching multiple nodes, pagination parameters are ignored. For large nodes use: pageSize: 10-25, summarizeNodes: true, depth: 1.', inputSchema: { type: 'object', properties: { file_key: { type: 'string', description: 'Figma file key' }, ids: { type: 'array', items: { type: 'string' }, description: 'Array of node IDs to retrieve' }, pageSize: { type: 'number', description: '[SINGLE NODE ONLY] Number of child nodes to fetch per page. Ignored when fetching multiple nodes.', minimum: 1, maximum: 1000 }, maxResponseSize: { type: 'number', description: 'Maximum response size in MB (defaults to 50)', minimum: 1, maximum: 100 }, cursor: { type: 'string', description: '[SINGLE NODE ONLY] Pagination cursor - child index to start from (e.g., "0", "50", "100"). Ignored when fetching multiple nodes.' }, depth: { type: 'number', description: 'Maximum depth to traverse in the node tree', minimum: 1 }, nodeTypes: { type: 'array', items: { type: 'string', enum: [ 'FRAME', 'GROUP', 'VECTOR', 'BOOLEAN_OPERATION', 'STAR', 'LINE', 'TEXT', 'COMPONENT', 'INSTANCE' ] }, description: 'Filter nodes by type. For single nodes: filters children only (parent always kept). For multiple nodes: filters all requested nodes.' }, excludeProps: { type: 'array', items: { type: 'string' }, description: 'Properties to exclude from node data' }, summarizeNodes: { type: 'boolean', description: 'Return only essential node properties to reduce response size' } }, required: ['file_key', 'ids'] } }
  • MCP CallToolRequestSchema handler case for 'get_file_nodes'. Validates arguments, calls figmaClient.getFileNodes, checks response size with helpful error if too large, and formats response.
    case 'get_file_nodes': { const args = request.params.arguments as unknown as FileNodesArgs & { pageSize?: number; maxResponseSize?: number; cursor?: string; depth?: number; nodeTypes?: string[]; excludeProps?: string[]; summarizeNodes?: boolean; }; if (!args.file_key) { throw new McpError(ErrorCode.InvalidParams, 'file_key is required'); } if (!args.ids || !Array.isArray(args.ids) || args.ids.length === 0) { throw new McpError( ErrorCode.InvalidParams, 'ids array is required and must not be empty' ); } console.debug('[MCP Debug] Fetching file nodes', args); const data = await this.figmaClient.getFileNodes( args.file_key, args.ids, { pageSize: args.pageSize, maxResponseSize: args.maxResponseSize, cursor: args.cursor, depth: args.depth, nodeTypes: args.nodeTypes, excludeProps: args.excludeProps, summarizeNodes: args.summarizeNodes } ); // Check response size and provide helpful error if too large const jsonString = JSON.stringify(data, null, 2); const sizeInBytes = new TextEncoder().encode(jsonString).length; const estimatedTokens = Math.ceil(sizeInBytes / 4); // ~4 bytes per token if (estimatedTokens > 25000) { const helpfulError = { error: 'Response too large', estimatedTokens, maxTokens: 25000, currentParams: { pageSize: args.pageSize || 50, depth: args.depth || 'full', summarizeNodes: args.summarizeNodes || false }, solutions: [ '1. Use summarizeNodes: true to strip nodes to essentials', '2. Reduce pageSize to 10-25 (fetches fewer children per request)', '3. Set depth: 1 to fetch only immediate children', '4. Add excludeProps: ["fills", "effects", "strokes", "exportSettings"]', '5. Use cursor to paginate through children (cursor: "0" for first batch, "10" for second, etc.)' ], exampleCall: { file_key: args.file_key, ids: args.ids, pageSize: 10, depth: 1, summarizeNodes: true, excludeProps: ["fills", "effects", "strokes"], cursor: "0" }, nodeInfo: data.pagination || { note: 'Multiple nodes requested - try fetching one at a time' } }; return { content: [{ type: 'text', text: JSON.stringify(helpfulError, null, 2) }], }; } return { content: [{ type: 'text', text: jsonString }], }; }
  • Input schema/JSON Schema for the get_file_nodes tool, defining all parameters, types, descriptions, constraints, and required fields.
    inputSchema: { type: 'object', properties: { file_key: { type: 'string', description: 'Figma file key' }, ids: { type: 'array', items: { type: 'string' }, description: 'Array of node IDs to retrieve' }, pageSize: { type: 'number', description: '[SINGLE NODE ONLY] Number of child nodes to fetch per page. Ignored when fetching multiple nodes.', minimum: 1, maximum: 1000 }, maxResponseSize: { type: 'number', description: 'Maximum response size in MB (defaults to 50)', minimum: 1, maximum: 100 }, cursor: { type: 'string', description: '[SINGLE NODE ONLY] Pagination cursor - child index to start from (e.g., "0", "50", "100"). Ignored when fetching multiple nodes.' }, depth: { type: 'number', description: 'Maximum depth to traverse in the node tree', minimum: 1 }, nodeTypes: { type: 'array', items: { type: 'string', enum: [ 'FRAME', 'GROUP', 'VECTOR', 'BOOLEAN_OPERATION', 'STAR', 'LINE', 'TEXT', 'COMPONENT', 'INSTANCE' ] }, description: 'Filter nodes by type. For single nodes: filters children only (parent always kept). For multiple nodes: filters all requested nodes.' }, excludeProps: { type: 'array', items: { type: 'string' }, description: 'Properties to exclude from node data' }, summarizeNodes: { type: 'boolean', description: 'Return only essential node properties to reduce response size' } }, required: ['file_key', 'ids'] }

Other Tools

Related Tools

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/ArchimedesCrypto/figma-mcp-chunked'

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