# Directory Structure ``` ├── .gitignore ├── dockerfile ├── package-lock.json ├── package.json ├── README.md ├── src │ ├── figma-api.ts │ ├── index.ts │ ├── prompts.ts │ ├── resources.ts │ └── tools.ts └── tsconfig.json ``` # Files -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- ``` # Dependencies node_modules/ npm-debug.log yarn-debug.log yarn-error.log # Build outputs build/ dist/ *.tsbuildinfo # Environment variables .env .env.local .env.development .env.test .env.production # IDE and editor files .idea/ .vscode/ *.swp *.swo .DS_Store # Logs logs/ *.log # Coverage reports coverage/ # Temporary files tmp/ temp/ ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown # Figma MCP Server A Model Context Protocol (MCP) server that connects to Figma's API, allowing AI tools and LLMs to access and work with your Figma designs. ## Features - **Design Data Extraction**: Extract components, styles, and text from your Figma designs - **Design System Analysis**: Analyze design system consistency and patterns - **UI Content Management**: Extract and organize all UI copy from designs - **Development Handoff**: Generate comprehensive documentation for developers - **Seamless AI Integration**: Connect your designs to AI tools like Claude, Cursor, and other MCP-compatible clients ## Getting Started ### Prerequisites - Node.js 16 or higher - Figma Personal Access Token (Get it from your Figma account settings) ### Installation 1. Clone the repository: ```bash git clone https://github.com/yourusername/figma-mcp-server.git cd figma-mcp-server ``` 2. Install dependencies: ```bash npm install ``` 3. Create a `.env` file in the project root: ``` FIGMA_API_TOKEN=your_figma_personal_access_token API_KEY=your_secure_api_key TRANSPORT_TYPE=stdio ``` 4. Build the server: ```bash npm run build ``` 5. Start the server: ```bash npm start ``` ## Connecting to Clients ### Claude for Desktop 1. Open or create the Claude for Desktop configuration file: - macOS: `~/Library/Application Support/Claude/claude_desktop_config.json` - Windows: `%APPDATA%\Claude\claude_desktop_config.json` 2. Add the following configuration: ```json { "mcpServers": { "figma": { "command": "node", "args": ["/absolute/path/to/figma-mcp-server/build/index.js"], "env": { "FIGMA_API_TOKEN": "your_figma_personal_access_token", "TRANSPORT_TYPE": "stdio" } } } } ``` 3. Restart Claude for Desktop ### Cursor #### Global Configuration Create or edit Cursor's MCP configuration file: - macOS: `~/Library/Application Support/Cursor/mcp.json` - Windows: `%APPDATA%\Cursor\mcp.json` ```json { "mcpServers": { "figma-mcp": { "url": "http://localhost:3000/sse", "env": { "API_KEY": "your_secure_api_key" } } } } ``` #### Project-Specific Configuration 1. Create a `.cursor` directory in your project root: ```bash mkdir -p .cursor ``` 2. Create an `mcp.json` file inside that directory: ```json { "mcpServers": { "figma-mcp": { "url": "http://localhost:3000/sse", "env": { "API_KEY": "your_secure_api_key" } } } } ``` ## Available Tools | Tool | Description | |------|-------------| | `get-file-info` | Get basic information about a Figma file | | `get-nodes` | Get specific nodes from a Figma file | | `get-components` | Get component information from a Figma file | | `get-styles` | Get style information from a Figma file | | `get-comments` | Get comments from a Figma file | | `search-file` | Search for elements in a Figma file by type, name, etc. | | `extract-text` | Extract all text elements from a Figma file | ## Available Prompts - `analyze-design-system` - Analyze design system components and styles for consistency - `extract-ui-copy` - Extract and organize all UI copy from designs - `generate-dev-handoff` - Generate development handoff documentation based on designs ## Usage Examples Using with Claude: ``` Can you analyze the design system in my Figma file with key abc123? Look for consistency in color usage and typography. ``` Using with Cursor: ``` Generate React components for the buttons from my Figma file with key abc123, using tailwind CSS. ``` ## Environment Variables | Variable | Description | Default | |----------|-------------|---------| | `FIGMA_API_TOKEN` | Your Figma Personal Access Token | (Required) | | `API_KEY` | Security key for API authentication | (Required) | | `TRANSPORT_TYPE` | Transport method (`stdio` or `sse`) | `stdio` | | `PORT` | Port for SSE transport | `3000` | ## Architecture This MCP server: 1. Connects to the Figma API using your personal access token 2. Exposes a standardized interface following the Model Context Protocol 3. Provides tools, resources, and prompts that LLMs can use to interact with your Figma designs 4. Supports both stdio transport (local connections) and SSE transport (remote connections) ## Contributing Contributions are welcome! Please feel free to submit a Pull Request. ``` -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- ```json { "compilerOptions": { "target": "ES2022", "module": "Node16", "moduleResolution": "Node16", "outDir": "./build", "rootDir": "./src", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true }, "include": ["src/**/*"], "exclude": ["node_modules"] } ``` -------------------------------------------------------------------------------- /dockerfile: -------------------------------------------------------------------------------- ```dockerfile FROM node:18-alpine WORKDIR /app # Copy package files and install dependencies COPY package*.json ./ RUN npm ci --only=production # Copy the built app COPY build/ ./build/ # Set environment variables (placeholder values to be overridden at runtime) ENV FIGMA_API_TOKEN="" ENV TRANSPORT_TYPE="sse" ENV PORT=3000 ENV API_KEY="" # Expose the port EXPOSE 3000 # Run the server CMD ["node", "build/index.js"] ``` -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- ```json { "name": "figma-mcp-server", "version": "1.0.0", "main": "index.js", "type": "module", "scripts": { "build": "tsc", "start": "node build/index.js", "dev": "tsc && node build/index.js", "start:stdio": "TRANSPORT_TYPE=stdio node build/index.js", "start:sse": "TRANSPORT_TYPE=sse node build/index.js" }, "keywords": [ "figma", "mcp", "design" ], "author": "Mohammed-uvaiz", "license": "ISC", "description": "", "dependencies": { "@modelcontextprotocol/sdk": "^1.7.0", "cors": "^2.8.5", "dotenv": "^16.4.7", "express": "^5.0.1", "node-fetch": "^3.3.2", "zod": "^3.24.2" }, "devDependencies": { "@types/cors": "^2.8.17", "@types/express": "^5.0.0", "@types/node": "^22.13.10", "typescript": "^5.8.2" } } ``` -------------------------------------------------------------------------------- /src/figma-api.ts: -------------------------------------------------------------------------------- ```typescript import fetch from "node-fetch"; import dotenv from "dotenv"; dotenv.config(); // Base URL for Figma API export const FIGMA_API_BASE_URL = "https://api.figma.com/v1"; // Check if Figma API token is available const FIGMA_API_TOKEN = process.env.FIGMA_API_TOKEN; if (!FIGMA_API_TOKEN) { console.error("Error: FIGMA_API_TOKEN environment variable is required"); process.exit(1); } /** * Helper function to make authenticated requests to Figma API * @param endpoint API endpoint path (without base URL) * @returns Promise with JSON response */ export async function fetchFigmaAPI(endpoint: string) { const response = await fetch(`${FIGMA_API_BASE_URL}${endpoint}`, { headers: { "X-Figma-Token": FIGMA_API_TOKEN as string } }); if (!response.ok) { throw new Error(`Figma API error: ${response.status} ${response.statusText}`); } return response.json(); } /** * Helper function to recursively search through nodes by criteria * @param node The node to search from * @param query The search query string * @returns Array of matching nodes */ export function searchNodes(node: any, query: string, results: any[] = []) { // Check if this node matches search criteria if (node.name && node.name.toLowerCase().includes(query.toLowerCase())) { results.push({ id: node.id, name: node.name, type: node.type, path: [node.name] }); } // Recursively search child nodes if they exist if (node.children && node.children.length > 0) { for (const child of node.children) { searchNodes(child, query, results); } } return results; } /** * Helper function to recursively find text nodes * @param node The node to search from * @returns Array of text nodes */ export function findTextNodes(node: any, results: any[] = []) { if (node.type === "TEXT") { results.push({ id: node.id, name: node.name, characters: node.characters, style: node.style }); } // Recursively search child nodes if they exist if (node.children && node.children.length > 0) { for (const child of node.children) { findTextNodes(child, results); } } return results; } ``` -------------------------------------------------------------------------------- /src/prompts.ts: -------------------------------------------------------------------------------- ```typescript import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { z } from "zod"; /** * Register all Figma-related prompts with the MCP server * @param server The MCP server instance */ export function registerPrompts(server: McpServer) { // Prompt to analyze design system consistency server.prompt( "analyze-design-system", "Analyze design system components and styles for consistency", { fileKey: z.string().describe("The Figma file key") }, ({ fileKey }) => ({ messages: [{ role: "user", content: { type: "text", text: `Please analyze the design system in this Figma file (${fileKey}). Focus on color consistency, typography usage, component variations, and any inconsistencies or opportunities for improvement. First list the components and styles using the appropriate tools, then provide your analysis.` } }] }) ); // Prompt to extract UI copy server.prompt( "extract-ui-copy", "Extract and organize all UI copy from designs", { fileKey: z.string().describe("The Figma file key") }, ({ fileKey }) => ({ messages: [{ role: "user", content: { type: "text", text: `Please extract all UI copy from this Figma file (${fileKey}). Organize the text by screens or components, and identify any potential inconsistencies in tone, terminology, or language. Use the extract-text tool to get the content.` } }] }) ); // Prompt to generate development handoff documentation server.prompt( "generate-dev-handoff", "Generate development handoff documentation based on designs", { fileKey: z.string().describe("The Figma file key") }, ({ fileKey }) => ({ messages: [{ role: "user", content: { type: "text", text: `Please create comprehensive development handoff documentation for this Figma file (${fileKey}). Include component specifications, style guides, interaction patterns, and responsive behavior descriptions. Use the appropriate tools to gather file data, components, and styles.` } }] }) ); } ``` -------------------------------------------------------------------------------- /src/resources.ts: -------------------------------------------------------------------------------- ```typescript import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js"; import { fetchFigmaAPI } from "./figma-api.js"; interface ComponentsResponse { meta: { components: unknown[]; }; } interface StylesResponse { meta: { styles: unknown[]; }; } /** * Register all Figma-related resources with the MCP server * @param server The MCP server instance */ export function registerResources(server: McpServer) { // Resource to list Figma files a user has access to server.resource( "user-files", "figma://files", async (uri) => { try { const filesData = await fetchFigmaAPI(`/me/files`); return { contents: [{ uri: uri.href, text: JSON.stringify(filesData, null, 2) }] }; } catch (error) { throw new Error(`Error fetching Figma files: ${error instanceof Error ? error.message : String(error)}`); } } ); // Resource to list projects server.resource( "projects", "figma://projects", async (uri) => { try { const projectsData = await fetchFigmaAPI(`/me/projects`); return { contents: [{ uri: uri.href, text: JSON.stringify(projectsData, null, 2) }] }; } catch (error) { throw new Error(`Error fetching Figma projects: ${error instanceof Error ? error.message : String(error)}`); } } ); // Resource to get file data server.resource( "file-data", new ResourceTemplate("figma://{fileKey}/data", { list: undefined }), async (uri, { fileKey }) => { try { const fileData = await fetchFigmaAPI(`/files/${fileKey}`); return { contents: [{ uri: uri.href, text: JSON.stringify(fileData, null, 2) }] }; } catch (error) { throw new Error(`Error fetching Figma file data: ${error instanceof Error ? error.message : String(error)}`); } } ); // Resource to get design system metadata server.resource( "design-system", new ResourceTemplate("figma://{fileKey}/design-system", { list: undefined }), async (uri, { fileKey }) => { try { // Get components and styles in parallel const [componentsData, stylesData] = await Promise.all([ fetchFigmaAPI(`/files/${fileKey}/components`) as Promise<ComponentsResponse>, fetchFigmaAPI(`/files/${fileKey}/styles`) as Promise<StylesResponse> ]); const designSystem = { components: componentsData.meta.components, styles: stylesData.meta.styles }; return { contents: [{ uri: uri.href, text: JSON.stringify(designSystem, null, 2) }] }; } catch (error) { throw new Error(`Error fetching design system data: ${error instanceof Error ? error.message : String(error)}`); } } ); } ``` -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- ```typescript import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; import express from "express"; import cors from "cors"; import dotenv from "dotenv"; // Import our components import { registerTools } from "./tools.js"; import { registerResources } from "./resources.js"; import { registerPrompts } from "./prompts.js"; // Load environment variables console.error("Loading environment variables..."); dotenv.config(); console.error("Environment loaded. FIGMA_API_TOKEN present:", process.env.FIGMA_API_TOKEN ? "Yes" : "No"); // Check token before proceeding if (!process.env.FIGMA_API_TOKEN) { console.error("ERROR: FIGMA_API_TOKEN environment variable is required!"); process.exit(1); } async function main() { try { console.error("===== FIGMA MCP SERVER STARTING ====="); // Create the MCP server console.error("Creating MCP server instance..."); const server = new McpServer({ name: "figma-mcp-server", version: "1.0.0" }); console.error("MCP server instance created successfully"); // Register all components console.error("Registering tools..."); registerTools(server); console.error("Registering resources..."); registerResources(server); console.error("Registering prompts..."); registerPrompts(server); console.error("All components registered successfully"); // Determine transport type based on environment const transportType = process.env.TRANSPORT_TYPE || "stdio"; console.error(`Using transport type: ${transportType}`); if (transportType === "stdio") { // Use stdio transport for local connections console.error("Creating StdioServerTransport..."); const transport = new StdioServerTransport(); console.error("Connecting server to stdio transport..."); await server.connect(transport); console.error("Successfully connected to stdio transport"); } else if (transportType === "sse") { // Use SSE transport for remote connections console.error("Starting web server for SSE transport..."); const app = express(); const port = process.env.PORT || 3000; // Add CORS support app.use(cors()); // Parse JSON bodies app.use(express.json()); // ADD AUTHENTICATION HERE const API_KEY = process.env.API_KEY || "default-key-please-change"; // Middleware to check for API key const authenticateApiKey = (req: express.Request, res: express.Response, next: express.NextFunction): void => { const providedKey = req.headers['x-api-key']; if (!providedKey || providedKey !== API_KEY) { res.status(401).json({ error: "Unauthorized" }); return; } next(); }; // Add a basic home page app.get("/", (req, res) => { res.send("Figma MCP Server - Status: Running"); }); // Health check endpoint app.get("/health", (req, res) => { res.json({ status: "ok", version: "1.0.0" }); }); // Map to store active transports const activeTransports = new Map(); // SSE endpoint app.get("/sse",authenticateApiKey, (req, res) => { console.error("New SSE connection request received"); const clientId = Date.now().toString(); res.setHeader("Content-Type", "text/event-stream"); res.setHeader("Cache-Control", "no-cache"); res.setHeader("Connection", "keep-alive"); // Create a new transport for this connection const transport = new SSEServerTransport("/messages", res); activeTransports.set(clientId, transport); console.error(`SSE connection established for client ${clientId}`); // Connect the server to this transport server.connect(transport).catch(err => { console.error(`Error connecting to transport for client ${clientId}:`, err); }); // Handle client disconnect req.on("close", () => { console.error(`Client ${clientId} disconnected`); activeTransports.delete(clientId); }); }); // Message endpoint for client-to-server communication app.post("/messages", authenticateApiKey,async (req, res) => { console.error("Received message from client"); // Find the active transport // Note: In a real implementation, you'd need a way to identify // which transport to use based on the client if (activeTransports.size > 0) { const transport = Array.from(activeTransports.values())[0]; await transport.handlePostMessage(req, res); } else { res.status(400).json({ error: "No active connections" }); } }); // Start the server app.listen(port, () => { console.error(`Web server running on port ${port}`); }); } else { console.error(`Unknown transport type: ${transportType}`); process.exit(1); } console.error("===== FIGMA MCP SERVER RUNNING ====="); } catch (error) { console.error("FATAL SERVER ERROR:"); console.error(error); process.exit(1); } } // Run the server console.error("Calling main()..."); main().catch(error => { console.error("Unhandled error in main():", error); process.exit(1); }); ``` -------------------------------------------------------------------------------- /src/tools.ts: -------------------------------------------------------------------------------- ```typescript import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { z } from "zod"; import { fetchFigmaAPI, searchNodes, findTextNodes } from "./figma-api.js"; interface FigmaFileResponse { name: string; lastModified: string; version: string; document: { id: string; name: string; type: string; }; schemaVersion: number; thumbnailUrl: string; } export function registerTools(server: McpServer) { // Tool to get file information server.tool( "get-file-info", "Get basic information about a Figma file", { fileKey: z.string().describe("The Figma file key (found in the file URL)") }, async ({ fileKey }) => { try { const fileData = await fetchFigmaAPI(`/files/${fileKey}`) as FigmaFileResponse; return { content: [ { type: "text", text: JSON.stringify({ name: fileData.name, lastModified: fileData.lastModified, version: fileData.version, document: { id: fileData.document.id, name: fileData.document.name, type: fileData.document.type }, schemaVersion: fileData.schemaVersion, thumbnailUrl: fileData.thumbnailUrl }, null, 2) } ] }; } catch (error) { return { content: [ { type: "text", text: `Error fetching Figma file information: ${error instanceof Error ? error.message : String(error)}` } ], isError: true }; } } ); // Tool to get file nodes server.tool( "get-nodes", "Get specific nodes from a Figma file", { fileKey: z.string().describe("The Figma file key (found in the file URL)"), nodeIds: z.array(z.string()).describe("Array of node IDs to fetch") }, async ({ fileKey, nodeIds }) => { try { const nodeIdsParam = nodeIds.join(","); const nodesData = await fetchFigmaAPI(`/files/${fileKey}/nodes?ids=${nodeIdsParam}`); return { content: [ { type: "text", text: JSON.stringify(nodesData, null, 2) } ] }; } catch (error) { return { content: [ { type: "text", text: `Error fetching Figma nodes: ${error instanceof Error ? error.message : String(error)}` } ], isError: true }; } } ); // Tool to get file component sets and components server.tool( "get-components", "Get component information from a Figma file", { fileKey: z.string().describe("The Figma file key (found in the file URL)") }, async ({ fileKey }) => { try { const componentsData = await fetchFigmaAPI(`/files/${fileKey}/components`); return { content: [ { type: "text", text: JSON.stringify(componentsData, null, 2) } ] }; } catch (error) { return { content: [ { type: "text", text: `Error fetching Figma components: ${error instanceof Error ? error.message : String(error)}` } ], isError: true }; } } ); // Tool to get design system styles server.tool( "get-styles", "Get style information from a Figma file", { fileKey: z.string().describe("The Figma file key (found in the file URL)") }, async ({ fileKey }) => { try { const stylesData = await fetchFigmaAPI(`/files/${fileKey}/styles`); return { content: [ { type: "text", text: JSON.stringify(stylesData, null, 2) } ] }; } catch (error) { return { content: [ { type: "text", text: `Error fetching Figma styles: ${error instanceof Error ? error.message : String(error)}` } ], isError: true }; } } ); // Tool to get comments from a Figma file server.tool( "get-comments", "Get comments from a Figma file", { fileKey: z.string().describe("The Figma file key (found in the file URL)") }, async ({ fileKey }) => { try { const commentsData = await fetchFigmaAPI(`/files/${fileKey}/comments`); return { content: [ { type: "text", text: JSON.stringify(commentsData, null, 2) } ] }; } catch (error) { return { content: [ { type: "text", text: `Error fetching Figma comments: ${error instanceof Error ? error.message : String(error)}` } ], isError: true }; } } ); // Tool to search for specific elements in a Figma file server.tool( "search-file", "Search for elements in a Figma file by type, name, etc.", { fileKey: z.string().describe("The Figma file key (found in the file URL)"), query: z.string().describe("Search query") }, async ({ fileKey, query }) => { try { // Fetch all file data first const fileData = await fetchFigmaAPI(`/files/${fileKey}`) as FigmaFileResponse; const searchResults = searchNodes(fileData.document, query); return { content: [ { type: "text", text: JSON.stringify(searchResults, null, 2) } ] }; } catch (error) { return { content: [ { type: "text", text: `Error searching Figma file: ${error instanceof Error ? error.message : String(error)}` } ], isError: true }; } } ); // Tool to extract text from a Figma file server.tool( "extract-text", "Extract all text elements from a Figma file", { fileKey: z.string().describe("The Figma file key (found in the file URL)") }, async ({ fileKey }) => { try { // Fetch all file data first const fileData = await fetchFigmaAPI(`/files/${fileKey}`) as FigmaFileResponse; const textNodes = findTextNodes(fileData.document); return { content: [ { type: "text", text: JSON.stringify(textNodes, null, 2) } ] }; } catch (error) { return { content: [ { type: "text", text: `Error extracting text from Figma file: ${error instanceof Error ? error.message : String(error)}` } ], isError: true }; } } ); } ```