This is page 1 of 3. Use http://codebase.md/sonnylazuardi/cursor-talk-to-figma-mcp?lines=true&page={x} to view the full context. # Directory Structure ``` ├── .gitignore ├── bun.lock ├── Dockerfile ├── DRAGME.md ├── LICENSE ├── package.json ├── readme.md ├── scripts │ └── setup.sh ├── smithery.yaml ├── src │ ├── cursor_mcp_plugin │ │ ├── code.js │ │ ├── manifest.json │ │ ├── setcharacters.js │ │ └── ui.html │ ├── socket.ts │ └── talk_to_figma_mcp │ ├── bun.lock │ ├── package.json │ ├── server.ts │ └── tsconfig.json ├── tsconfig.json └── tsup.config.ts ``` # Files -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- ``` 1 | node_modules/ 2 | .cursor/ 3 | dist/ 4 | docs/ 5 | ``` -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- ```markdown 1 | # Cursor Talk to Figma MCP 2 | 3 | This project implements a Model Context Protocol (MCP) integration between Cursor AI and Figma, allowing Cursor to communicate with Figma for reading designs and modifying them programmatically. 4 | 5 | https://github.com/user-attachments/assets/129a14d2-ed73-470f-9a4c-2240b2a4885c 6 | 7 | ## Project Structure 8 | 9 | - `src/talk_to_figma_mcp/` - TypeScript MCP server for Figma integration 10 | - `src/cursor_mcp_plugin/` - Figma plugin for communicating with Cursor 11 | - `src/socket.ts` - WebSocket server that facilitates communication between the MCP server and Figma plugin 12 | 13 | ## Get Started 14 | 15 | 1. Install Bun if you haven't already: 16 | 17 | ```bash 18 | curl -fsSL https://bun.sh/install | bash 19 | ``` 20 | 21 | 2. Run setup, this will also install MCP in your Cursor's active project 22 | 23 | ```bash 24 | bun setup 25 | ``` 26 | 27 | 3. Start the Websocket server 28 | 29 | ```bash 30 | bun socket 31 | ``` 32 | 33 | 4. **NEW** Install Figma plugin from [Figma community page](https://www.figma.com/community/plugin/1485687494525374295/cursor-talk-to-figma-mcp-plugin) or [install locally](#figma-plugin) 34 | 35 | ## Quick Video Tutorial 36 | 37 | [Video Link](https://www.linkedin.com/posts/sonnylazuardi_just-wanted-to-share-my-latest-experiment-activity-7307821553654657024-yrh8) 38 | 39 | ## Design Automation Example 40 | 41 | **Bulk text content replacement** 42 | 43 | Thanks to [@dusskapark](https://github.com/dusskapark) for contributing the bulk text replacement feature. Here is the [demo video](https://www.youtube.com/watch?v=j05gGT3xfCs). 44 | 45 | **Instance Override Propagation** 46 | Another contribution from [@dusskapark](https://github.com/dusskapark) 47 | Propagate component instance overrides from a source instance to multiple target instances with a single command. This feature dramatically reduces repetitive design work when working with component instances that need similar customizations. Check out our [demo video](https://youtu.be/uvuT8LByroI). 48 | 49 | ## Development Setup 50 | 51 | To develop, update your mcp config to direct to your local directory. 52 | 53 | ```json 54 | { 55 | "mcpServers": { 56 | "TalkToFigma": { 57 | "command": "bun", 58 | "args": ["/path-to-repo/src/talk_to_figma_mcp/server.ts"] 59 | } 60 | } 61 | } 62 | ``` 63 | 64 | ## Manual Setup and Installation 65 | 66 | ### MCP Server: Integration with Cursor 67 | 68 | Add the server to your Cursor MCP configuration in `~/.cursor/mcp.json`: 69 | 70 | ```json 71 | { 72 | "mcpServers": { 73 | "TalkToFigma": { 74 | "command": "bunx", 75 | "args": ["cursor-talk-to-figma-mcp@latest"] 76 | } 77 | } 78 | } 79 | ``` 80 | 81 | ### WebSocket Server 82 | 83 | Start the WebSocket server: 84 | 85 | ```bash 86 | bun socket 87 | ``` 88 | 89 | ### Figma Plugin 90 | 91 | 1. In Figma, go to Plugins > Development > New Plugin 92 | 2. Choose "Link existing plugin" 93 | 3. Select the `src/cursor_mcp_plugin/manifest.json` file 94 | 4. The plugin should now be available in your Figma development plugins 95 | 96 | ## Windows + WSL Guide 97 | 98 | 1. Install bun via powershell 99 | 100 | ```bash 101 | powershell -c "irm bun.sh/install.ps1|iex" 102 | ``` 103 | 104 | 2. Uncomment the hostname `0.0.0.0` in `src/socket.ts` 105 | 106 | ```typescript 107 | // uncomment this to allow connections in windows wsl 108 | hostname: "0.0.0.0", 109 | ``` 110 | 111 | 3. Start the websocket 112 | 113 | ```bash 114 | bun socket 115 | ``` 116 | 117 | ## Usage 118 | 119 | 1. Start the WebSocket server 120 | 2. Install the MCP server in Cursor 121 | 3. Open Figma and run the Cursor MCP Plugin 122 | 4. Connect the plugin to the WebSocket server by joining a channel using `join_channel` 123 | 5. Use Cursor to communicate with Figma using the MCP tools 124 | 125 | ## MCP Tools 126 | 127 | The MCP server provides the following tools for interacting with Figma: 128 | 129 | ### Document & Selection 130 | 131 | - `get_document_info` - Get information about the current Figma document 132 | - `get_selection` - Get information about the current selection 133 | - `read_my_design` - Get detailed node information about the current selection without parameters 134 | - `get_node_info` - Get detailed information about a specific node 135 | - `get_nodes_info` - Get detailed information about multiple nodes by providing an array of node IDs 136 | - `set_focus` - Set focus on a specific node by selecting it and scrolling viewport to it 137 | - `set_selections` - Set selection to multiple nodes and scroll viewport to show them 138 | 139 | ### Annotations 140 | 141 | - `get_annotations` - Get all annotations in the current document or specific node 142 | - `set_annotation` - Create or update an annotation with markdown support 143 | - `set_multiple_annotations` - Batch create/update multiple annotations efficiently 144 | - `scan_nodes_by_types` - Scan for nodes with specific types (useful for finding annotation targets) 145 | 146 | ### Prototyping & Connections 147 | 148 | - `get_reactions` - Get all prototype reactions from nodes with visual highlight animation 149 | - `set_default_connector` - Set a copied FigJam connector as the default connector style for creating connections (must be set before creating connections) 150 | - `create_connections` - Create FigJam connector lines between nodes, based on prototype flows or custom mapping 151 | 152 | ### Creating Elements 153 | 154 | - `create_rectangle` - Create a new rectangle with position, size, and optional name 155 | - `create_frame` - Create a new frame with position, size, and optional name 156 | - `create_text` - Create a new text node with customizable font properties 157 | 158 | ### Modifying text content 159 | 160 | - `scan_text_nodes` - Scan text nodes with intelligent chunking for large designs 161 | - `set_text_content` - Set the text content of a single text node 162 | - `set_multiple_text_contents` - Batch update multiple text nodes efficiently 163 | 164 | ### Auto Layout & Spacing 165 | 166 | - `set_layout_mode` - Set the layout mode and wrap behavior of a frame (NONE, HORIZONTAL, VERTICAL) 167 | - `set_padding` - Set padding values for an auto-layout frame (top, right, bottom, left) 168 | - `set_axis_align` - Set primary and counter axis alignment for auto-layout frames 169 | - `set_layout_sizing` - Set horizontal and vertical sizing modes for auto-layout frames (FIXED, HUG, FILL) 170 | - `set_item_spacing` - Set distance between children in an auto-layout frame 171 | 172 | ### Styling 173 | 174 | - `set_fill_color` - Set the fill color of a node (RGBA) 175 | - `set_stroke_color` - Set the stroke color and weight of a node 176 | - `set_corner_radius` - Set the corner radius of a node with optional per-corner control 177 | 178 | ### Layout & Organization 179 | 180 | - `move_node` - Move a node to a new position 181 | - `resize_node` - Resize a node with new dimensions 182 | - `delete_node` - Delete a node 183 | - `delete_multiple_nodes` - Delete multiple nodes at once efficiently 184 | - `clone_node` - Create a copy of an existing node with optional position offset 185 | 186 | ### Components & Styles 187 | 188 | - `get_styles` - Get information about local styles 189 | - `get_local_components` - Get information about local components 190 | - `create_component_instance` - Create an instance of a component 191 | - `get_instance_overrides` - Extract override properties from a selected component instance 192 | - `set_instance_overrides` - Apply extracted overrides to target instances 193 | 194 | ### Export & Advanced 195 | 196 | - `export_node_as_image` - Export a node as an image (PNG, JPG, SVG, or PDF) - limited support on image currently returning base64 as text 197 | 198 | ### Connection Management 199 | 200 | - `join_channel` - Join a specific channel to communicate with Figma 201 | 202 | ### MCP Prompts 203 | 204 | The MCP server includes several helper prompts to guide you through complex design tasks: 205 | 206 | - `design_strategy` - Best practices for working with Figma designs 207 | - `read_design_strategy` - Best practices for reading Figma designs 208 | - `text_replacement_strategy` - Systematic approach for replacing text in Figma designs 209 | - `annotation_conversion_strategy` - Strategy for converting manual annotations to Figma's native annotations 210 | - `swap_overrides_instances` - Strategy for transferring overrides between component instances in Figma 211 | - `reaction_to_connector_strategy` - Strategy for converting Figma prototype reactions to connector lines using the output of 'get_reactions', and guiding the use 'create_connections' in sequence 212 | 213 | ## Development 214 | 215 | ### Building the Figma Plugin 216 | 217 | 1. Navigate to the Figma plugin directory: 218 | 219 | ``` 220 | cd src/cursor_mcp_plugin 221 | ``` 222 | 223 | 2. Edit code.js and ui.html 224 | 225 | ## Best Practices 226 | 227 | When working with the Figma MCP: 228 | 229 | 1. Always join a channel before sending commands 230 | 2. Get document overview using `get_document_info` first 231 | 3. Check current selection with `get_selection` before modifications 232 | 4. Use appropriate creation tools based on needs: 233 | - `create_frame` for containers 234 | - `create_rectangle` for basic shapes 235 | - `create_text` for text elements 236 | 5. Verify changes using `get_node_info` 237 | 6. Use component instances when possible for consistency 238 | 7. Handle errors appropriately as all commands can throw exceptions 239 | 8. For large designs: 240 | - Use chunking parameters in `scan_text_nodes` 241 | - Monitor progress through WebSocket updates 242 | - Implement appropriate error handling 243 | 9. For text operations: 244 | - Use batch operations when possible 245 | - Consider structural relationships 246 | - Verify changes with targeted exports 247 | 10. For converting legacy annotations: 248 | - Scan text nodes to identify numbered markers and descriptions 249 | - Use `scan_nodes_by_types` to find UI elements that annotations refer to 250 | - Match markers with their target elements using path, name, or proximity 251 | - Categorize annotations appropriately with `get_annotations` 252 | - Create native annotations with `set_multiple_annotations` in batches 253 | - Verify all annotations are properly linked to their targets 254 | - Delete legacy annotation nodes after successful conversion 255 | 11. Visualize prototype noodles as FigJam connectors: 256 | 257 | - Use `get_reactions` to extract prototype flows, 258 | - set a default connector with `set_default_connector`, 259 | - and generate connector lines with `create_connections` for clear visual flow mapping. 260 | 261 | ## License 262 | 263 | MIT 264 | ``` -------------------------------------------------------------------------------- /tsup.config.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { defineConfig } from 'tsup'; 2 | 3 | export default defineConfig({ 4 | entry: ['src/talk_to_figma_mcp/server.ts'], 5 | format: ['cjs', 'esm'], 6 | dts: true, 7 | clean: true, 8 | outDir: 'dist', 9 | target: 'node18', 10 | sourcemap: true, 11 | minify: false, 12 | splitting: false, 13 | bundle: true, 14 | }); ``` -------------------------------------------------------------------------------- /scripts/setup.sh: -------------------------------------------------------------------------------- ```bash 1 | #!/bin/bash 2 | 3 | # Create .cursor directory if it doesn't exist 4 | mkdir -p .cursor 5 | 6 | bun install 7 | 8 | # Create mcp.json with the current directory path 9 | echo "{ 10 | \"mcpServers\": { 11 | \"TalkToFigma\": { 12 | \"command\": \"bunx\", 13 | \"args\": [ 14 | \"cursor-talk-to-figma-mcp@latest\" 15 | ] 16 | } 17 | } 18 | }" > .cursor/mcp.json ``` -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- ```dockerfile 1 | # Use the Bun image as the base image 2 | FROM oven/bun:latest 3 | 4 | # Set the working directory in the container 5 | WORKDIR /app 6 | 7 | # Copy the current directory contents into the container at /app 8 | COPY package*.json ./ 9 | 10 | RUN bun install 11 | 12 | # Expose the port on which the API will listen 13 | EXPOSE 3055 14 | 15 | # Run the server when the container launches 16 | CMD ["bun", "src/talk_to_figma_mcp/server.ts"] ``` -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "ESNext", 5 | "moduleResolution": "bundler", 6 | "esModuleInterop": true, 7 | "strict": false, 8 | "skipLibCheck": true, 9 | "declaration": true, 10 | "outDir": "dist", 11 | "rootDir": "src", 12 | "lib": ["ES2022"], 13 | "types": ["bun-types"] 14 | }, 15 | "include": ["src/**/*"], 16 | "exclude": ["node_modules", "dist"] 17 | } 18 | ``` -------------------------------------------------------------------------------- /smithery.yaml: -------------------------------------------------------------------------------- ```yaml 1 | # Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml 2 | 3 | startCommand: 4 | type: stdio 5 | configSchema: 6 | # JSON Schema defining the configuration options for the MCP. 7 | {} 8 | commandFunction: 9 | # A JS function that produces the CLI command based on the given config to start the MCP on stdio. 10 | |- 11 | (config) => ({ 12 | command: 'bunx', 13 | args: ['cursor-talk-to-figma-mcp'] 14 | }) 15 | ``` -------------------------------------------------------------------------------- /src/talk_to_figma_mcp/tsconfig.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "NodeNext", 5 | "moduleResolution": "NodeNext", 6 | "strict": false, 7 | "esModuleInterop": true, 8 | "skipLibCheck": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "outDir": "dist", 11 | "rootDir": ".", 12 | "declaration": true, 13 | "experimentalDecorators": false, 14 | "emitDecoratorMetadata": false 15 | }, 16 | "include": ["./**/*.ts"], 17 | "exclude": ["node_modules", "dist"] 18 | } 19 | ``` -------------------------------------------------------------------------------- /src/cursor_mcp_plugin/manifest.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "name": "Cursor MCP Plugin", 3 | "id": "cursor-mcp-plugin", 4 | "api": "1.0.0", 5 | "main": "code.js", 6 | "ui": "ui.html", 7 | "editorType": [ 8 | "figma", 9 | "figjam" 10 | ], 11 | "permissions": [], 12 | "networkAccess": { 13 | "allowedDomains": [ 14 | "https://google.com" 15 | ], 16 | "devAllowedDomains": [ 17 | "http://localhost:3055", 18 | "ws://localhost:3055" 19 | ] 20 | }, 21 | "documentAccess": "dynamic-page", 22 | "enableProposedApi": true, 23 | "enablePrivatePluginApi": true 24 | } ``` -------------------------------------------------------------------------------- /src/talk_to_figma_mcp/package.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "name": "talk-to-figma-mcp", 3 | "version": "1.0.0", 4 | "description": "MCP server for Figma integration", 5 | "main": "server.ts", 6 | "type": "module", 7 | "scripts": { 8 | "start": "node --loader ts-node/esm server.ts", 9 | "build": "tsc", 10 | "dev": "node --loader ts-node/esm --watch server.ts" 11 | }, 12 | "keywords": [ 13 | "figma", 14 | "mcp", 15 | "cursor", 16 | "ai" 17 | ], 18 | "dependencies": { 19 | "@modelcontextprotocol/sdk": "1.13.1", 20 | "uuid": "^9.0.1", 21 | "ws": "^8.16.0", 22 | "zod": "3.22.4" 23 | }, 24 | "devDependencies": { 25 | "@types/node": "^20.10.5", 26 | "@types/uuid": "^9.0.7", 27 | "@types/ws": "^8.5.10", 28 | "ts-node": "^10.9.2", 29 | "typescript": "^5.3.3" 30 | } 31 | } ``` -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "name": "cursor-talk-to-figma-mcp", 3 | "description": "Cursor Talk to Figma MCP", 4 | "version": "0.3.3", 5 | "module": "dist/server.js", 6 | "main": "dist/server.js", 7 | "bin": { 8 | "cursor-talk-to-figma-mcp": "dist/server.js" 9 | }, 10 | "files": [ 11 | "dist", 12 | "README.md" 13 | ], 14 | "type": "module", 15 | "scripts": { 16 | "start": "bun run dist/server.js", 17 | "socket": "bun run src/socket.ts", 18 | "setup": "./scripts/setup.sh", 19 | "build": "tsup", 20 | "build:watch": "tsup --watch", 21 | "dev": "bun run build:watch", 22 | "pub:release": "bun run build && npm publish" 23 | }, 24 | "devDependencies": { 25 | "@types/bun": "latest", 26 | "bun-types": "^1.2.5", 27 | "tsup": "^8.4.0", 28 | "typescript": "^5.0.0" 29 | }, 30 | "dependencies": { 31 | "@modelcontextprotocol/sdk": "1.13.1", 32 | "uuid": "latest", 33 | "ws": "latest", 34 | "zod": "3.22.4" 35 | } 36 | } ``` -------------------------------------------------------------------------------- /src/socket.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { Server, ServerWebSocket } from "bun"; 2 | 3 | // Store clients by channel 4 | const channels = new Map<string, Set<ServerWebSocket<any>>>(); 5 | 6 | function handleConnection(ws: ServerWebSocket<any>) { 7 | // Don't add to clients immediately - wait for channel join 8 | console.log("New client connected"); 9 | 10 | // Send welcome message to the new client 11 | ws.send(JSON.stringify({ 12 | type: "system", 13 | message: "Please join a channel to start chatting", 14 | })); 15 | 16 | ws.close = () => { 17 | console.log("Client disconnected"); 18 | 19 | // Remove client from their channel 20 | channels.forEach((clients, channelName) => { 21 | if (clients.has(ws)) { 22 | clients.delete(ws); 23 | 24 | // Notify other clients in same channel 25 | clients.forEach((client) => { 26 | if (client.readyState === WebSocket.OPEN) { 27 | client.send(JSON.stringify({ 28 | type: "system", 29 | message: "A user has left the channel", 30 | channel: channelName 31 | })); 32 | } 33 | }); 34 | } 35 | }); 36 | }; 37 | } 38 | 39 | const server = Bun.serve({ 40 | port: 3055, 41 | // uncomment this to allow connections in windows wsl 42 | // hostname: "0.0.0.0", 43 | fetch(req: Request, server: Server) { 44 | // Handle CORS preflight 45 | if (req.method === "OPTIONS") { 46 | return new Response(null, { 47 | headers: { 48 | "Access-Control-Allow-Origin": "*", 49 | "Access-Control-Allow-Methods": "GET, POST, OPTIONS", 50 | "Access-Control-Allow-Headers": "Content-Type, Authorization", 51 | }, 52 | }); 53 | } 54 | 55 | // Handle WebSocket upgrade 56 | const success = server.upgrade(req, { 57 | headers: { 58 | "Access-Control-Allow-Origin": "*", 59 | }, 60 | }); 61 | 62 | if (success) { 63 | return; // Upgraded to WebSocket 64 | } 65 | 66 | // Return response for non-WebSocket requests 67 | return new Response("WebSocket server running", { 68 | headers: { 69 | "Access-Control-Allow-Origin": "*", 70 | }, 71 | }); 72 | }, 73 | websocket: { 74 | open: handleConnection, 75 | message(ws: ServerWebSocket<any>, message: string | Buffer) { 76 | try { 77 | console.log("Received message from client:", message); 78 | const data = JSON.parse(message as string); 79 | 80 | if (data.type === "join") { 81 | const channelName = data.channel; 82 | if (!channelName || typeof channelName !== "string") { 83 | ws.send(JSON.stringify({ 84 | type: "error", 85 | message: "Channel name is required" 86 | })); 87 | return; 88 | } 89 | 90 | // Create channel if it doesn't exist 91 | if (!channels.has(channelName)) { 92 | channels.set(channelName, new Set()); 93 | } 94 | 95 | // Add client to channel 96 | const channelClients = channels.get(channelName)!; 97 | channelClients.add(ws); 98 | 99 | // Notify client they joined successfully 100 | ws.send(JSON.stringify({ 101 | type: "system", 102 | message: `Joined channel: ${channelName}`, 103 | channel: channelName 104 | })); 105 | 106 | console.log("Sending message to client:", data.id); 107 | 108 | ws.send(JSON.stringify({ 109 | type: "system", 110 | message: { 111 | id: data.id, 112 | result: "Connected to channel: " + channelName, 113 | }, 114 | channel: channelName 115 | })); 116 | 117 | // Notify other clients in channel 118 | channelClients.forEach((client) => { 119 | if (client !== ws && client.readyState === WebSocket.OPEN) { 120 | client.send(JSON.stringify({ 121 | type: "system", 122 | message: "A new user has joined the channel", 123 | channel: channelName 124 | })); 125 | } 126 | }); 127 | return; 128 | } 129 | 130 | // Handle regular messages 131 | if (data.type === "message") { 132 | const channelName = data.channel; 133 | if (!channelName || typeof channelName !== "string") { 134 | ws.send(JSON.stringify({ 135 | type: "error", 136 | message: "Channel name is required" 137 | })); 138 | return; 139 | } 140 | 141 | const channelClients = channels.get(channelName); 142 | if (!channelClients || !channelClients.has(ws)) { 143 | ws.send(JSON.stringify({ 144 | type: "error", 145 | message: "You must join the channel first" 146 | })); 147 | return; 148 | } 149 | 150 | // Broadcast to all clients in the channel 151 | channelClients.forEach((client) => { 152 | if (client.readyState === WebSocket.OPEN) { 153 | console.log("Broadcasting message to client:", data.message); 154 | client.send(JSON.stringify({ 155 | type: "broadcast", 156 | message: data.message, 157 | sender: client === ws ? "You" : "User", 158 | channel: channelName 159 | })); 160 | } 161 | }); 162 | } 163 | } catch (err) { 164 | console.error("Error handling message:", err); 165 | } 166 | }, 167 | close(ws: ServerWebSocket<any>) { 168 | // Remove client from their channel 169 | channels.forEach((clients) => { 170 | clients.delete(ws); 171 | }); 172 | } 173 | } 174 | }); 175 | 176 | console.log(`WebSocket server running on port ${server.port}`); 177 | ``` -------------------------------------------------------------------------------- /src/cursor_mcp_plugin/setcharacters.js: -------------------------------------------------------------------------------- ```javascript 1 | function uniqBy(arr, predicate) { 2 | const cb = typeof predicate === "function" ? predicate : (o) => o[predicate]; 3 | return [ 4 | ...arr 5 | .reduce((map, item) => { 6 | const key = item === null || item === undefined ? item : cb(item); 7 | 8 | map.has(key) || map.set(key, item); 9 | 10 | return map; 11 | }, new Map()) 12 | .values(), 13 | ]; 14 | } 15 | export const setCharacters = async (node, characters, options) => { 16 | const fallbackFont = options?.fallbackFont || { 17 | family: "Roboto", 18 | style: "Regular", 19 | }; 20 | try { 21 | if (node.fontName === figma.mixed) { 22 | if (options?.smartStrategy === "prevail") { 23 | const fontHashTree = {}; 24 | for (let i = 1; i < node.characters.length; i++) { 25 | const charFont = node.getRangeFontName(i - 1, i); 26 | const key = `${charFont.family}::${charFont.style}`; 27 | fontHashTree[key] = fontHashTree[key] ? fontHashTree[key] + 1 : 1; 28 | } 29 | const prevailedTreeItem = Object.entries(fontHashTree).sort( 30 | (a, b) => b[1] - a[1] 31 | )[0]; 32 | const [family, style] = prevailedTreeItem[0].split("::"); 33 | const prevailedFont = { 34 | family, 35 | style, 36 | }; 37 | await figma.loadFontAsync(prevailedFont); 38 | node.fontName = prevailedFont; 39 | } else if (options?.smartStrategy === "strict") { 40 | return setCharactersWithStrictMatchFont(node, characters, fallbackFont); 41 | } else if (options?.smartStrategy === "experimental") { 42 | return setCharactersWithSmartMatchFont(node, characters, fallbackFont); 43 | } else { 44 | const firstCharFont = node.getRangeFontName(0, 1); 45 | await figma.loadFontAsync(firstCharFont); 46 | node.fontName = firstCharFont; 47 | } 48 | } else { 49 | await figma.loadFontAsync({ 50 | family: node.fontName.family, 51 | style: node.fontName.style, 52 | }); 53 | } 54 | } catch (err) { 55 | console.warn( 56 | `Failed to load "${node.fontName["family"]} ${node.fontName["style"]}" font and replaced with fallback "${fallbackFont.family} ${fallbackFont.style}"`, 57 | err 58 | ); 59 | await figma.loadFontAsync(fallbackFont); 60 | node.fontName = fallbackFont; 61 | } 62 | try { 63 | node.characters = characters; 64 | return true; 65 | } catch (err) { 66 | console.warn(`Failed to set characters. Skipped.`, err); 67 | return false; 68 | } 69 | }; 70 | 71 | const setCharactersWithStrictMatchFont = async ( 72 | node, 73 | characters, 74 | fallbackFont 75 | ) => { 76 | const fontHashTree = {}; 77 | for (let i = 1; i < node.characters.length; i++) { 78 | const startIdx = i - 1; 79 | const startCharFont = node.getRangeFontName(startIdx, i); 80 | const startCharFontVal = `${startCharFont.family}::${startCharFont.style}`; 81 | while (i < node.characters.length) { 82 | i++; 83 | const charFont = node.getRangeFontName(i - 1, i); 84 | if (startCharFontVal !== `${charFont.family}::${charFont.style}`) { 85 | break; 86 | } 87 | } 88 | fontHashTree[`${startIdx}_${i}`] = startCharFontVal; 89 | } 90 | await figma.loadFontAsync(fallbackFont); 91 | node.fontName = fallbackFont; 92 | node.characters = characters; 93 | console.log(fontHashTree); 94 | await Promise.all( 95 | Object.keys(fontHashTree).map(async (range) => { 96 | console.log(range, fontHashTree[range]); 97 | const [start, end] = range.split("_"); 98 | const [family, style] = fontHashTree[range].split("::"); 99 | const matchedFont = { 100 | family, 101 | style, 102 | }; 103 | await figma.loadFontAsync(matchedFont); 104 | return node.setRangeFontName(Number(start), Number(end), matchedFont); 105 | }) 106 | ); 107 | return true; 108 | }; 109 | 110 | const getDelimiterPos = (str, delimiter, startIdx = 0, endIdx = str.length) => { 111 | const indices = []; 112 | let temp = startIdx; 113 | for (let i = startIdx; i < endIdx; i++) { 114 | if ( 115 | str[i] === delimiter && 116 | i + startIdx !== endIdx && 117 | temp !== i + startIdx 118 | ) { 119 | indices.push([temp, i + startIdx]); 120 | temp = i + startIdx + 1; 121 | } 122 | } 123 | temp !== endIdx && indices.push([temp, endIdx]); 124 | return indices.filter(Boolean); 125 | }; 126 | 127 | const buildLinearOrder = (node) => { 128 | const fontTree = []; 129 | const newLinesPos = getDelimiterPos(node.characters, "\n"); 130 | newLinesPos.forEach(([newLinesRangeStart, newLinesRangeEnd], n) => { 131 | const newLinesRangeFont = node.getRangeFontName( 132 | newLinesRangeStart, 133 | newLinesRangeEnd 134 | ); 135 | if (newLinesRangeFont === figma.mixed) { 136 | const spacesPos = getDelimiterPos( 137 | node.characters, 138 | " ", 139 | newLinesRangeStart, 140 | newLinesRangeEnd 141 | ); 142 | spacesPos.forEach(([spacesRangeStart, spacesRangeEnd], s) => { 143 | const spacesRangeFont = node.getRangeFontName( 144 | spacesRangeStart, 145 | spacesRangeEnd 146 | ); 147 | if (spacesRangeFont === figma.mixed) { 148 | const spacesRangeFont = node.getRangeFontName( 149 | spacesRangeStart, 150 | spacesRangeStart[0] 151 | ); 152 | fontTree.push({ 153 | start: spacesRangeStart, 154 | delimiter: " ", 155 | family: spacesRangeFont.family, 156 | style: spacesRangeFont.style, 157 | }); 158 | } else { 159 | fontTree.push({ 160 | start: spacesRangeStart, 161 | delimiter: " ", 162 | family: spacesRangeFont.family, 163 | style: spacesRangeFont.style, 164 | }); 165 | } 166 | }); 167 | } else { 168 | fontTree.push({ 169 | start: newLinesRangeStart, 170 | delimiter: "\n", 171 | family: newLinesRangeFont.family, 172 | style: newLinesRangeFont.style, 173 | }); 174 | } 175 | }); 176 | return fontTree 177 | .sort((a, b) => +a.start - +b.start) 178 | .map(({ family, style, delimiter }) => ({ family, style, delimiter })); 179 | }; 180 | 181 | const setCharactersWithSmartMatchFont = async ( 182 | node, 183 | characters, 184 | fallbackFont 185 | ) => { 186 | const rangeTree = buildLinearOrder(node); 187 | const fontsToLoad = uniqBy( 188 | rangeTree, 189 | ({ family, style }) => `${family}::${style}` 190 | ).map(({ family, style }) => ({ 191 | family, 192 | style, 193 | })); 194 | 195 | await Promise.all([...fontsToLoad, fallbackFont].map(figma.loadFontAsync)); 196 | 197 | node.fontName = fallbackFont; 198 | node.characters = characters; 199 | 200 | let prevPos = 0; 201 | rangeTree.forEach(({ family, style, delimiter }) => { 202 | if (prevPos < node.characters.length) { 203 | const delimeterPos = node.characters.indexOf(delimiter, prevPos); 204 | const endPos = 205 | delimeterPos > prevPos ? delimeterPos : node.characters.length; 206 | const matchedFont = { 207 | family, 208 | style, 209 | }; 210 | node.setRangeFontName(prevPos, endPos, matchedFont); 211 | prevPos = endPos + 1; 212 | } 213 | }); 214 | return true; 215 | }; 216 | ``` -------------------------------------------------------------------------------- /src/cursor_mcp_plugin/ui.html: -------------------------------------------------------------------------------- ```html 1 | <!DOCTYPE html> 2 | <html> 3 | <head> 4 | <meta charset="utf-8" /> 5 | <title>Cursor MCP Plugin</title> 6 | <style> 7 | body { 8 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, 9 | Helvetica, Arial, sans-serif; 10 | margin: 0; 11 | padding: 20px; 12 | color: #e0e0e0; 13 | background-color: #1e1e1e; 14 | } 15 | .container { 16 | display: flex; 17 | flex-direction: column; 18 | height: 100%; 19 | } 20 | h1 { 21 | font-size: 16px; 22 | font-weight: 600; 23 | margin-bottom: 10px; 24 | color: #ffffff; 25 | } 26 | h2 { 27 | font-size: 14px; 28 | font-weight: 600; 29 | margin-top: 20px; 30 | margin-bottom: 8px; 31 | color: #ffffff; 32 | } 33 | button { 34 | background-color: #18a0fb; 35 | border: none; 36 | color: white; 37 | padding: 8px 12px; 38 | border-radius: 6px; 39 | margin-top: 8px; 40 | margin-bottom: 8px; 41 | cursor: pointer; 42 | font-size: 14px; 43 | transition: background-color 0.2s; 44 | } 45 | button:hover { 46 | background-color: #0d8ee0; 47 | } 48 | button.secondary { 49 | background-color: #3d3d3d; 50 | color: #e0e0e0; 51 | } 52 | button.secondary:hover { 53 | background-color: #4d4d4d; 54 | } 55 | button:disabled { 56 | background-color: #333333; 57 | color: #666666; 58 | cursor: not-allowed; 59 | } 60 | input { 61 | border: 1px solid #444444; 62 | border-radius: 4px; 63 | padding: 8px; 64 | margin-bottom: 12px; 65 | font-size: 14px; 66 | width: 100%; 67 | box-sizing: border-box; 68 | background-color: #2d2d2d; 69 | color: #e0e0e0; 70 | } 71 | label { 72 | display: block; 73 | margin-bottom: 4px; 74 | font-size: 12px; 75 | font-weight: 500; 76 | color: #cccccc; 77 | } 78 | .status { 79 | margin-top: 16px; 80 | padding: 12px; 81 | border-radius: 6px; 82 | font-size: 14px; 83 | } 84 | .status.connected { 85 | background-color: #1a472a; 86 | color: #4ade80; 87 | } 88 | .status.disconnected { 89 | background-color: #471a1a; 90 | color: #ff9999; 91 | } 92 | .status.info { 93 | background-color: #1a3147; 94 | color: #66b3ff; 95 | } 96 | .section { 97 | margin-bottom: 24px; 98 | } 99 | .hidden { 100 | display: none; 101 | } 102 | .logo { 103 | width: 50px; 104 | height: 50px; 105 | } 106 | .header { 107 | display: flex; 108 | align-items: center; 109 | margin-bottom: 16px; 110 | } 111 | .header-text { 112 | margin-left: 12px; 113 | } 114 | .header-text h1 { 115 | margin: 0; 116 | font-size: 16px; 117 | } 118 | .header-text p { 119 | margin: 4px 0 0 0; 120 | font-size: 12px; 121 | color: #999999; 122 | } 123 | .tabs { 124 | display: flex; 125 | border-bottom: 1px solid #444444; 126 | margin-bottom: 16px; 127 | } 128 | .tab { 129 | padding: 8px 16px; 130 | cursor: pointer; 131 | font-size: 14px; 132 | font-weight: 500; 133 | color: #999999; 134 | } 135 | .tab.active { 136 | border-bottom: 2px solid #18a0fb; 137 | color: #18a0fb; 138 | } 139 | .tab-content { 140 | display: none; 141 | } 142 | .tab-content.active { 143 | display: block; 144 | } 145 | .link { 146 | color: #18a0fb; 147 | text-decoration: none; 148 | cursor: pointer; 149 | } 150 | .link:hover { 151 | text-decoration: underline; 152 | } 153 | .header-logo { 154 | padding: 16px; 155 | border-radius: 16px; 156 | background-color: #333; 157 | } 158 | .header-logo-image { 159 | width: 24px; 160 | height: 24px; 161 | object-fit: contain; 162 | } 163 | /* Progress styles */ 164 | .operation-complete { 165 | color: #4ade80; 166 | } 167 | .operation-error { 168 | color: #ff9999; 169 | } 170 | </style> 171 | </head> 172 | <body> 173 | <div class="container"> 174 | <div class="header"> 175 | <div class="header-logo"> 176 | <img 177 | class="header-logo-image" 178 | src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAMAAAANIilAAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAJcEhZcwAAEJwAABCcASbNOjQAAAB1UExURUdwTP////////////////39/f////////////////////////////7+/v////////////39/f////////////////////////////////////////////////////39/fn5+ejo6P///+rq6uXl5f////Ly8gf4a04AAAAkdFJOUwAOdkZCfz04zIgbT0pkIagnm7C9b6C2LWqSxBMyB11W2Ovsy3D12ZYAAALtSURBVEjHndcJt6ogEADgXNAUcWlxSQVN3/3/P/EBAgJpWdM9p5ue78xANE2n05vIUduffgvn1oA0bX+hvRc1DYjTPHe+tiGIoqhx4zTNq/y72lMURQtmqasuPc4dAmgwfWuZrqquiw8uNnC5BRJT3YXhIZ7Xris0oLjlmOrArz7VHpOb6wpNee0ITVMHvvd25/qgvtFwla8dpxV7xnTi7dbed7iuTY16lZoV7iXQb3cqRgjVgoviKTZSUw2719pbD2OEVu5yjnqeOpZ75lMMobVzfUcwC6lrofGJpdb3jGtj6TkkNKRWtXMsU+ciNdfQUwe+zZ7/vo1CYYgv39G/kShMS6mHL+g8F96K2Uqi52E6j3DFnsc4uR/hMwugYd9bOLoeSTvPE1yx4/sLh9B9fKbziHVM3z/G+dKb5wdKdysxsNCc4+2l/yk7EnrOVhwGBt9auqJ0t9gR13C4cl77bdil88SPuK9jxrXksHjab48Mwo+4ha3aSbZJ52JpC4GFbY7OdsVst4Lls/mKZe1y6fXTonS3RFsIN7C5dAJsO+WiI21jbd8xesFEtoUdLLjH+qGNJ9WRuj3MOOQNycaV6khvsLc0MxsD2Uq7bhcHuBZh4rFdujjT1c6GkaXtszCx3sW3MRRfNjwiI7EjGjGfFjZwUgM9CuNggqRVXz+vOGDTBOCP5UnHE73ghjK1jYNlEIma9UnHBb/qdkvq1MSQjk4yCvGk4UneQylLbWAIio3I1t26q4sNTuM01tqQe9+My5pYv9wk8Ypv92w7JpXYulGoD8aJ3C/bUUp8tW5EuTa2oXI7ZGLzahZYE0l03QqZWI8Lfh1lw+zxEoNIrF8Dm/NQT8rzgz+WP/oQmL6Ud4pud/4DZzMWPKjXZfJufOyiVzzKV4/609yelDaWiZsDc6+DSwOLxNqxeD/6Ah3zf674+Kyf3xUeDi3WDFIKzCpOv/5phB4MD+cs/OWXVdych/GBf/xJd4pL9+1i/wOElMO5v/co4wAAAABJRU5ErkJggg==" 179 | /> 180 | </div> 181 | <div class="header-text"> 182 | <h1>Cursor Talk To Figma Plugin</h1> 183 | <p>Connect Figma to Cursor AI using MCP</p> 184 | </div> 185 | </div> 186 | 187 | <div class="tabs"> 188 | <div id="tab-connection" class="tab active">Connection</div> 189 | <div id="tab-about" class="tab">About</div> 190 | </div> 191 | 192 | <div id="content-connection" class="tab-content active"> 193 | <div class="section"> 194 | <label for="port">WebSocket Server Port</label> 195 | <div style="display: flex; gap: 8px"> 196 | <input 197 | type="number" 198 | id="port" 199 | placeholder="3055" 200 | value="3055" 201 | min="1024" 202 | max="65535" 203 | /> 204 | <button id="btn-connect" class="primary">Connect</button> 205 | </div> 206 | </div> 207 | 208 | <div id="connection-status" class="status disconnected"> 209 | Not connected to Cursor MCP server 210 | </div> 211 | 212 | <div class="section"> 213 | <button id="btn-disconnect" class="secondary" disabled> 214 | Disconnect 215 | </button> 216 | </div> 217 | 218 | <!-- Add Progress Bar Section --> 219 | <div id="progress-container" class="section hidden"> 220 | <h2>Operation Progress</h2> 221 | <div id="progress-message">No operation in progress</div> 222 | <div style="width: 100%; background-color: #444; border-radius: 4px; margin-top: 8px;"> 223 | <div id="progress-bar" style="width: 0%; height: 8px; background-color: #18a0fb; border-radius: 4px; transition: width 0.3s;"></div> 224 | </div> 225 | <div style="display: flex; justify-content: space-between; margin-top: 4px; font-size: 12px;"> 226 | <div id="progress-status">Not started</div> 227 | <div id="progress-percentage">0%</div> 228 | </div> 229 | </div> 230 | </div> 231 | 232 | <div id="content-about" class="tab-content"> 233 | <div class="section"> 234 | <h2>About Cursor Talk To Figma Plugin</h2> 235 | <p> 236 | This plugin allows Cursor AI to communicate with Figma, enabling 237 | AI-assisted design operations. 238 | <a 239 | class="link" 240 | onclick="window.open(`https://github.com/grab/cursor-talk-to-figma-mcp`, '_blank')" 241 | >Github</a 242 | > 243 | </p> 244 | <p>Version: 1.0.0</p> 245 | 246 | <h2>How to Use</h2> 247 | <ol> 248 | <li>Make sure the MCP server is running in Cursor</li> 249 | <li>Connect to the server using the port number (default: 3055)</li> 250 | <li>Once connected, you can interact with Figma through Cursor</li> 251 | </ol> 252 | </div> 253 | </div> 254 | </div> 255 | 256 | <script> 257 | // WebSocket connection state 258 | const state = { 259 | connected: false, 260 | socket: null, 261 | serverPort: 3055, 262 | pendingRequests: new Map(), 263 | channel: null, 264 | }; 265 | 266 | // UI Elements 267 | const portInput = document.getElementById("port"); 268 | const connectButton = document.getElementById("btn-connect"); 269 | const disconnectButton = document.getElementById("btn-disconnect"); 270 | const connectionStatus = document.getElementById("connection-status"); 271 | 272 | // Tabs 273 | const tabs = document.querySelectorAll(".tab"); 274 | const tabContents = document.querySelectorAll(".tab-content"); 275 | 276 | // Add UI elements for progress tracking 277 | const progressContainer = document.getElementById("progress-container"); 278 | const progressBar = document.getElementById("progress-bar"); 279 | const progressMessage = document.getElementById("progress-message"); 280 | const progressStatus = document.getElementById("progress-status"); 281 | const progressPercentage = document.getElementById("progress-percentage"); 282 | 283 | // Initialize UI 284 | function updateConnectionStatus(isConnected, message) { 285 | state.connected = isConnected; 286 | connectionStatus.innerHTML = 287 | message || 288 | (isConnected 289 | ? "Connected to Cursor MCP server" 290 | : "Not connected to Cursor MCP server"); 291 | connectionStatus.className = `status ${ 292 | isConnected ? "connected" : "disconnected" 293 | }`; 294 | 295 | connectButton.disabled = isConnected; 296 | disconnectButton.disabled = !isConnected; 297 | portInput.disabled = isConnected; 298 | } 299 | 300 | // Connect to WebSocket server 301 | async function connectToServer(port) { 302 | try { 303 | if (state.connected && state.socket) { 304 | updateConnectionStatus(true, "Already connected to server"); 305 | return; 306 | } 307 | 308 | state.serverPort = port; 309 | state.socket = new WebSocket(`ws://localhost:${port}`); 310 | 311 | state.socket.onopen = () => { 312 | // Generate random channel name 313 | const channelName = generateChannelName(); 314 | console.log("Joining channel:", channelName); 315 | state.channel = channelName; 316 | 317 | // Join the channel using the same format as App.tsx 318 | state.socket.send( 319 | JSON.stringify({ 320 | type: "join", 321 | channel: channelName.trim(), 322 | }) 323 | ); 324 | }; 325 | 326 | state.socket.onmessage = (event) => { 327 | try { 328 | const data = JSON.parse(event.data); 329 | console.log("Received message:", data); 330 | 331 | if (data.type === "system") { 332 | // Successfully joined channel 333 | if (data.message && data.message.result) { 334 | state.connected = true; 335 | const channelName = data.channel; 336 | updateConnectionStatus( 337 | true, 338 | `Connected to server on port ${port} in channel: <strong>${channelName}</strong>` 339 | ); 340 | 341 | // Notify the plugin code 342 | parent.postMessage( 343 | { 344 | pluginMessage: { 345 | type: "notify", 346 | message: `Connected to Cursor MCP server on port ${port} in channel: ${channelName}`, 347 | }, 348 | }, 349 | "*" 350 | ); 351 | } 352 | } else if (data.type === "error") { 353 | console.error("Error:", data.message); 354 | updateConnectionStatus(false, `Error: ${data.message}`); 355 | state.socket.close(); 356 | } 357 | 358 | handleSocketMessage(data); 359 | } catch (error) { 360 | console.error("Error parsing message:", error); 361 | } 362 | }; 363 | 364 | state.socket.onclose = () => { 365 | state.connected = false; 366 | state.socket = null; 367 | updateConnectionStatus(false, "Disconnected from server"); 368 | }; 369 | 370 | state.socket.onerror = (error) => { 371 | console.error("WebSocket error:", error); 372 | updateConnectionStatus(false, "Connection error"); 373 | state.connected = false; 374 | state.socket = null; 375 | }; 376 | } catch (error) { 377 | console.error("Connection error:", error); 378 | updateConnectionStatus( 379 | false, 380 | `Connection error: ${error.message || "Unknown error"}` 381 | ); 382 | } 383 | } 384 | 385 | // Disconnect from websocket server 386 | function disconnectFromServer() { 387 | if (state.socket) { 388 | state.socket.close(); 389 | state.socket = null; 390 | state.connected = false; 391 | updateConnectionStatus(false, "Disconnected from server"); 392 | } 393 | } 394 | 395 | // Handle messages from the WebSocket 396 | async function handleSocketMessage(payload) { 397 | const data = payload.message; 398 | console.log("handleSocketMessage", data); 399 | 400 | // If it's a response to a previous request 401 | if (data.id && state.pendingRequests.has(data.id)) { 402 | const { resolve, reject } = state.pendingRequests.get(data.id); 403 | state.pendingRequests.delete(data.id); 404 | 405 | if (data.error) { 406 | reject(new Error(data.error)); 407 | } else { 408 | resolve(data.result); 409 | } 410 | return; 411 | } 412 | 413 | // If it's a new command 414 | if (data.command) { 415 | try { 416 | // Send the command to the plugin code 417 | parent.postMessage( 418 | { 419 | pluginMessage: { 420 | type: "execute-command", 421 | id: data.id, 422 | command: data.command, 423 | params: data.params, 424 | }, 425 | }, 426 | "*" 427 | ); 428 | } catch (error) { 429 | // Send error back to WebSocket 430 | sendErrorResponse( 431 | data.id, 432 | error.message || "Error executing command" 433 | ); 434 | } 435 | } 436 | } 437 | 438 | // Send a command to the WebSocket server 439 | async function sendCommand(command, params) { 440 | return new Promise((resolve, reject) => { 441 | if (!state.connected || !state.socket) { 442 | reject(new Error("Not connected to server")); 443 | return; 444 | } 445 | 446 | const id = generateId(); 447 | state.pendingRequests.set(id, { resolve, reject }); 448 | 449 | state.socket.send( 450 | JSON.stringify({ 451 | id, 452 | type: "message", 453 | channel: state.channel, 454 | message: { 455 | id, 456 | command, 457 | params, 458 | }, 459 | }) 460 | ); 461 | 462 | // Set timeout to reject the promise after 30 seconds 463 | setTimeout(() => { 464 | if (state.pendingRequests.has(id)) { 465 | state.pendingRequests.delete(id); 466 | reject(new Error("Request timed out")); 467 | } 468 | }, 30000); 469 | }); 470 | } 471 | 472 | // Send success response back to WebSocket 473 | function sendSuccessResponse(id, result) { 474 | if (!state.connected || !state.socket) { 475 | console.error("Cannot send response: socket not connected"); 476 | return; 477 | } 478 | 479 | state.socket.send( 480 | JSON.stringify({ 481 | id, 482 | type: "message", 483 | channel: state.channel, 484 | message: { 485 | id, 486 | result, 487 | }, 488 | }) 489 | ); 490 | } 491 | 492 | // Send error response back to WebSocket 493 | function sendErrorResponse(id, errorMessage) { 494 | if (!state.connected || !state.socket) { 495 | console.error("Cannot send error response: socket not connected"); 496 | return; 497 | } 498 | 499 | state.socket.send( 500 | JSON.stringify({ 501 | id, 502 | type: "message", 503 | channel: state.channel, 504 | message: { 505 | id, 506 | error: errorMessage, 507 | result: {} 508 | }, 509 | }) 510 | ); 511 | } 512 | 513 | // Helper to generate unique IDs 514 | function generateId() { 515 | return ( 516 | Date.now().toString(36) + Math.random().toString(36).substr(2, 5) 517 | ); 518 | } 519 | 520 | // Add this function after the generateId() function 521 | function generateChannelName() { 522 | const characters = "abcdefghijklmnopqrstuvwxyz0123456789"; 523 | let result = ""; 524 | for (let i = 0; i < 8; i++) { 525 | result += characters.charAt( 526 | Math.floor(Math.random() * characters.length) 527 | ); 528 | } 529 | return result; 530 | } 531 | 532 | // Tab switching 533 | tabs.forEach((tab) => { 534 | tab.addEventListener("click", () => { 535 | tabs.forEach((t) => t.classList.remove("active")); 536 | tabContents.forEach((c) => c.classList.remove("active")); 537 | 538 | tab.classList.add("active"); 539 | const contentId = "content-" + tab.id.split("-")[1]; 540 | document.getElementById(contentId).classList.add("active"); 541 | }); 542 | }); 543 | 544 | // Connect to server 545 | connectButton.addEventListener("click", () => { 546 | const port = parseInt(portInput.value, 10) || 3055; 547 | updateConnectionStatus(false, "Connecting..."); 548 | connectionStatus.className = "status info"; 549 | connectToServer(port); 550 | }); 551 | 552 | // Disconnect from server 553 | disconnectButton.addEventListener("click", () => { 554 | updateConnectionStatus(false, "Disconnecting..."); 555 | connectionStatus.className = "status info"; 556 | disconnectFromServer(); 557 | }); 558 | 559 | // Function to update progress UI 560 | function updateProgressUI(progressData) { 561 | // Show progress container if hidden 562 | progressContainer.classList.remove("hidden"); 563 | 564 | // Update progress bar 565 | const progress = progressData.progress || 0; 566 | progressBar.style.width = `${progress}%`; 567 | progressPercentage.textContent = `${progress}%`; 568 | 569 | // Update message 570 | progressMessage.textContent = progressData.message || "Operation in progress"; 571 | 572 | // Update status text based on operation state 573 | if (progressData.status === 'started') { 574 | progressStatus.textContent = "Started"; 575 | progressStatus.className = ""; 576 | } else if (progressData.status === 'in_progress') { 577 | progressStatus.textContent = "In Progress"; 578 | progressStatus.className = ""; 579 | } else if (progressData.status === 'completed') { 580 | progressStatus.textContent = "Completed"; 581 | progressStatus.className = "operation-complete"; 582 | 583 | // Hide progress container after 5 seconds 584 | setTimeout(() => { 585 | progressContainer.classList.add("hidden"); 586 | }, 5000); 587 | } else if (progressData.status === 'error') { 588 | progressStatus.textContent = "Error"; 589 | progressStatus.className = "operation-error"; 590 | } 591 | } 592 | 593 | // Send operation progress update to server 594 | function sendProgressUpdateToServer(progressData) { 595 | if (!state.connected || !state.socket) { 596 | console.error("Cannot send progress update: socket not connected"); 597 | return; 598 | } 599 | 600 | console.log("Sending progress update to server:", progressData); 601 | 602 | state.socket.send( 603 | JSON.stringify({ 604 | id: progressData.commandId, 605 | type: "progress_update", 606 | channel: state.channel, 607 | message: { 608 | id: progressData.commandId, 609 | type: "progress_update", 610 | data: progressData 611 | } 612 | }) 613 | ); 614 | } 615 | 616 | // Reset progress UI 617 | function resetProgressUI() { 618 | progressContainer.classList.add("hidden"); 619 | progressBar.style.width = "0%"; 620 | progressMessage.textContent = "No operation in progress"; 621 | progressStatus.textContent = "Not started"; 622 | progressStatus.className = ""; 623 | progressPercentage.textContent = "0%"; 624 | } 625 | 626 | // Listen for messages from the plugin code 627 | window.onmessage = (event) => { 628 | const message = event.data.pluginMessage; 629 | if (!message) return; 630 | 631 | console.log("Received message from plugin:", message); 632 | 633 | switch (message.type) { 634 | case "connection-status": 635 | updateConnectionStatus(message.connected, message.message); 636 | break; 637 | case "auto-connect": 638 | connectButton.click(); 639 | break; 640 | case "auto-disconnect": 641 | disconnectButton.click(); 642 | break; 643 | case "command-result": 644 | // Forward the result from plugin code back to WebSocket 645 | sendSuccessResponse(message.id, message.result); 646 | break; 647 | case "command-error": 648 | // Forward the error from plugin code back to WebSocket 649 | sendErrorResponse(message.id, message.error); 650 | break; 651 | case "command_progress": 652 | // Update UI with progress information 653 | updateProgressUI(message); 654 | // Forward progress update to server 655 | sendProgressUpdateToServer(message); 656 | break; 657 | } 658 | }; 659 | </script> 660 | </body> 661 | </html> ``` -------------------------------------------------------------------------------- /DRAGME.md: -------------------------------------------------------------------------------- ```markdown 1 | # README for AI Agent: Cursor-Figma MCP Setup Guide 2 | 3 | ## 🤖 AI Agent Decision Flow 4 | 5 | ```mermaid 6 | flowchart TD 7 | A[Start Setup] --> B{System Check} 8 | B -->|✅ Pass| C{Admin Access?} 9 | B -->|❌ Failed| Z[❌ Fix Prerequisites] 10 | 11 | C -->|✅ Yes| D[Method A: Full Install<br/>Homebrew + Bun] 12 | C -->|❌ No| E[Method B: Cursor-Only<br/>No System Changes] 13 | 14 | D --> F[Clone in Cursor] 15 | E --> F 16 | 17 | F --> G[Configure MCP] 18 | G --> H{MCP Connected?} 19 | H -->|✅ Yes| I[Start WebSocket] 20 | H -->|❌ No| J[Debug & Retry] 21 | 22 | I --> K[Install Figma Plugin] 23 | K --> L[Test Integration] 24 | L --> M[✅ Complete] 25 | 26 | J --> H 27 | ``` 28 | 29 | ## 🎯 Objective 30 | 31 | Install and configure Cursor-Figma MCP integration with **AI-agent-friendly decision trees** and fail-safe checkpoints at each step. This guide uses Cursor's built-in Git clone functionality to directly clone the official repository, simplifying the setup process. 32 | 33 | ### 🤖 For AI Agents Reading This Guide 34 | 35 | This guide is **specifically designed for AI agents** to understand, parse, and execute setup instructions autonomously. Each section includes: 36 | 37 | - **Clear boolean decision points** for branching logic 38 | - **Explicit command verification steps** with expected outputs 39 | - **Fallback strategies** when primary methods fail 40 | - **Environment-specific instructions** based on system capabilities 41 | - **Checkpoint validations** before proceeding to next steps 42 | 43 | **AI Agent Instructions**: 44 | 1. Execute each command and verify the expected output 45 | 2. Follow the branching logic based on system environment 46 | 3. If any step fails, use the provided fallback methods 47 | 4. Always validate checkpoints before continuing 48 | 5. **CRITICAL**: Create and update visual progress tracking using Mermaid diagrams at each major checkpoint 49 | 50 | ## 📋 System Environment Detection 51 | 52 | **CRITICAL FOR AI AGENTS**: Execute these detection commands and use results for branching decisions. 53 | 54 | ```bash 55 | # System Detection Commands - Execute ALL and store results 56 | echo "=== SYSTEM ENVIRONMENT DETECTION ===" 57 | 58 | # 1. Operating System Check 59 | OS_TYPE=$(uname -s) 60 | echo "OS_TYPE: $OS_TYPE" 61 | 62 | # 2. Shell Type Detection 63 | echo "SHELL_TYPE: $SHELL" 64 | 65 | # 3. Homebrew Availability 66 | HOMEBREW_AVAILABLE=$(which brew >/dev/null 2>&1 && echo "YES" || echo "NO") 67 | echo "HOMEBREW_AVAILABLE: $HOMEBREW_AVAILABLE" 68 | 69 | # 4. Node.js Availability 70 | NODE_AVAILABLE=$(which node >/dev/null 2>&1 && echo "YES" || echo "NO") 71 | if [ "$NODE_AVAILABLE" = "YES" ]; then 72 | NODE_VERSION=$(node --version 2>/dev/null || echo "UNKNOWN") 73 | echo "NODE_VERSION: $NODE_VERSION" 74 | fi 75 | 76 | # 5. Bun Availability 77 | BUN_AVAILABLE=$(which bun >/dev/null 2>&1 && echo "YES" || echo "NO") 78 | if [ "$BUN_AVAILABLE" = "YES" ]; then 79 | BUN_VERSION=$(bun --version 2>/dev/null || echo "UNKNOWN") 80 | echo "BUN_VERSION: $BUN_VERSION" 81 | fi 82 | 83 | # 6. Cursor IDE Availability 84 | CURSOR_AVAILABLE=$(which cursor >/dev/null 2>&1 && echo "YES" || echo "NO") 85 | echo "CURSOR_AVAILABLE: $CURSOR_AVAILABLE" 86 | 87 | # 7. Git Availability 88 | GIT_AVAILABLE=$(which git >/dev/null 2>&1 && echo "YES" || echo "NO") 89 | if [ "$GIT_AVAILABLE" = "YES" ]; then 90 | GIT_VERSION=$(git --version 2>/dev/null || echo "UNKNOWN") 91 | echo "GIT_VERSION: $GIT_VERSION" 92 | fi 93 | 94 | # 8. Admin/Sudo Access Check 95 | SUDO_AVAILABLE=$(sudo -n true 2>/dev/null && echo "YES" || echo "NO") 96 | echo "SUDO_AVAILABLE: $SUDO_AVAILABLE" 97 | 98 | echo "=== DETECTION COMPLETE ===" 99 | ``` 100 | 101 | ## 📊 AI Agent Progress Tracking Templates 102 | 103 | **MANDATORY FOR AI AGENTS**: Use these Mermaid templates to show progress at each checkpoint. 104 | 105 | ### 🎯 Progress Status Template 106 | 107 | Use these exact status indicators: 108 | - ✅ **COMPLETED** - Step finished successfully 109 | - 🔄 **CURRENT** - Currently executing this step 110 | - ⏳ **PENDING** - Not started yet 111 | - ❌ **FAILED** - Step failed, needs attention 112 | - ⚠️ **WARNING** - Step completed with warnings 113 | 114 | ### 📋 Standard Progress Tracking Template 115 | 116 | **AI AGENTS MUST CREATE THIS DIAGRAM** after environment detection and update it at each checkpoint: 117 | 118 | ```mermaid 119 | graph TD 120 | A[🔍 Environment Detection] --> B[📦 Method Selection] 121 | B --> C[⚡ Dependency Installation] 122 | C --> D[⚙️ MCP setup and configuration] 123 | D --> E[🌐 WebSocket Server] 124 | E --> F[🔌 Figma Plugin] 125 | F --> G[🧪 Integration Test] 126 | G --> H[✅ Setup Complete] 127 | 128 | classDef completed fill:#22c55e,stroke:#16a34a,stroke-width:2px,color:#fff 129 | classDef current fill:#3b82f6,stroke:#2563eb,stroke-width:2px,color:#fff 130 | classDef pending fill:#6b7280,stroke:#4b5563,stroke-width:2px,color:#fff 131 | classDef failed fill:#ef4444,stroke:#dc2626,stroke-width:2px,color:#fff 132 | classDef warning fill:#f59e0b,stroke:#d97706,stroke-width:2px,color:#fff 133 | ``` 134 | 135 | ### 🎨 How to Update Progress Status 136 | 137 | **AI Agents must use this exact format** at each checkpoint: 138 | 139 | ``` 140 | ## 📊 Current Progress Status 141 | 142 | ```mermaid 143 | graph TD 144 | A[🔍 Environment Detection] --> B[📦 Method Selection] 145 | B --> C[⚡ Dependency Installation] 146 | C --> D[⚙️ MCP setup and configuration] 147 | D --> E[🌐 WebSocket Server] 148 | E --> F[🔌 Figma Plugin] 149 | F --> G[🧪 Integration Test] 150 | G --> H[✅ Setup Complete] 151 | 152 | class A completed 153 | class B current 154 | class C,D,E,F,G,H pending 155 | 156 | classDef completed fill:#22c55e,stroke:#16a34a,stroke-width:2px,color:#fff 157 | classDef current fill:#3b82f6,stroke:#2563eb,stroke-width:2px,color:#fff 158 | classDef pending fill:#6b7280,stroke:#4b5563,stroke-width:2px,color:#fff 159 | classDef failed fill:#ef4444,stroke:#dc2626,stroke-width:2px,color:#fff 160 | classDef warning fill:#f59e0b,stroke:#d97706,stroke-width:2px,color:#fff 161 | ``` 162 | 163 | 164 | 165 | ### 🛠️ Method-Specific Progress Templates 166 | 167 | #### For Method A (Full Installation): 168 | ```mermaid 169 | graph LR 170 | A1[🍺 Homebrew Check] --> A2[📦 Install Dependencies] 171 | A2 --> A3[⚙️ MCP Setup] 172 | A3 --> A4[✅ Ready for WebSocket] 173 | ``` 174 | 175 | #### For Method B (Cursor-Only): 176 | ```mermaid 177 | graph LR 178 | B1[🖥️ Project Already Open] --> B2[📖 Setup Dependencies] 179 | B2 --> B3[⚙️ MCP Setup] 180 | B3 --> B4[✅ Ready for WebSocket] 181 | ``` 182 | 183 | ### 📍 Mandatory Progress Update Points 184 | 185 | **AI Agents MUST update progress diagram at these checkpoints:** 186 | 187 | 1. **After Environment Detection** (Step 1) 188 | 2. **After Method Selection** (Step 2) 189 | 3. **After Dependency Installation** (Step 3) 190 | 4. **After MCP Setup and Configuration** (Step 4) 191 | 5. **After WebSocket Start** (Step 5) 192 | 6. **After Figma Plugin Install** (Step 6) 193 | 7. **After Integration Test** (Step 7) 194 | 8. **Final Completion** (Step 8) 195 | 196 | ### 🤖 AI Agent Progress Update Commands 197 | 198 | **Use these exact commands at each checkpoint:** 199 | 200 | ``` 201 | CHECKPOINT [STEP_NUMBER]: [STEP_NAME] 202 | 203 | Please create/update the progress tracking diagram to show: 204 | - Current step: [STEP_NAME] 205 | - Status: [✅ COMPLETED / 🔄 CURRENT / ❌ FAILED] 206 | - Next step: [NEXT_STEP_NAME] 207 | - Any warnings or issues encountered 208 | 209 | Update the Mermaid diagram with appropriate class assignments and show the overall progress percentage. 210 | ``` 211 | 212 | ### 🤖 AI Agent Decision Logic 213 | 214 | Based on the detection results above, AI agents should follow this decision tree: 215 | 216 | ```bash 217 | # AI Agent Decision Variables (set based on detection above) 218 | HOMEBREW_AVAILABLE="YES" # or "NO" 219 | NODE_AVAILABLE="YES" # or "NO" 220 | BUN_AVAILABLE="YES" # or "NO" 221 | CURSOR_AVAILABLE="YES" # or "NO" 222 | SUDO_AVAILABLE="YES" # or "NO" 223 | ``` 224 | 225 | **Decision Tree**: 226 | - IF `HOMEBREW_AVAILABLE="YES"` AND `SUDO_AVAILABLE="YES"` → **Method A1: Homebrew Installation** 227 | - ELIF `SUDO_AVAILABLE="YES"` AND `HOMEBREW_AVAILABLE="NO"` → **Method A2: Direct Installation** 228 | - ELIF `CURSOR_AVAILABLE="YES"` AND `SUDO_AVAILABLE="NO"` → **Method B: Cursor-Only Setup** 229 | - ELSE → **Error: Insufficient System Access** 230 | 231 | ## 🛡️ Method Selection Based on Environment 232 | 233 | ### 🌟 Method A1: Homebrew Installation (Recommended) 234 | **Conditions**: `HOMEBREW_AVAILABLE="YES"` AND `SUDO_AVAILABLE="YES"` 235 | - Uses Homebrew package manager 236 | - Cleanest dependency management 237 | - Best for macOS systems with Homebrew 238 | 239 | ### 🔧 Method A2: Direct Installation 240 | **Conditions**: `SUDO_AVAILABLE="YES"` AND `HOMEBREW_AVAILABLE="NO"` 241 | - Direct installation without package manager 242 | - For systems without Homebrew but with admin access 243 | - Manual dependency management 244 | 245 | ### 📱 Method B: Cursor-Only Setup 246 | **Conditions**: `CURSOR_AVAILABLE="YES"` AND `SUDO_AVAILABLE="NO"` 247 | - No system-wide installations 248 | - Uses Cursor's built-in capabilities 249 | - For restricted environments 250 | 251 | --- 252 | 253 | ## 🚀 Method A1: Homebrew Installation 254 | 255 | ### 📊 Progress Tracking for Method A1 256 | 257 | **AI AGENTS**: Start by creating initial progress diagram: 258 | 259 | ``` 260 | CHECKPOINT 1: STARTING METHOD A1 - HOMEBREW INSTALLATION 261 | 262 | Please create the Method A1 progress tracking diagram: 263 | 264 | ```mermaid 265 | graph TD 266 | A[🔍 Environment Detection] --> B[📦 Method A1 Selected] 267 | B --> C[🍺 Homebrew Check] 268 | C --> D[📦 Install Dependencies] 269 | D --> E[⚙️ MCP Setup] 270 | E --> F[🚀 Ready for WebSocket] 271 | 272 | class A completed 273 | class B current 274 | class C,D,E,F pending 275 | 276 | classDef completed fill:#22c55e,stroke:#16a34a,stroke-width:2px,color:#fff 277 | classDef current fill:#3b82f6,stroke:#2563eb,stroke-width:2px,color:#fff 278 | classDef pending fill:#6b7280,stroke:#4b5563,stroke-width:2px,color:#fff 279 | ``` 280 | ``` 281 | 282 | ### Prerequisites Check for Method A1 283 | ```bash 284 | # Verify Homebrew functionality 285 | brew --version && echo "✅ Homebrew functional" || echo "❌ ERROR: Homebrew broken" 286 | 287 | # Check brew permissions 288 | brew doctor && echo "✅ Homebrew healthy" || echo "⚠️ WARNING: Homebrew has issues" 289 | ``` 290 | 291 | ### Step A1.1: Install Dependencies via Homebrew 292 | 293 | ```bash 294 | # Update Homebrew first 295 | brew update 296 | 297 | # Install Node.js if not available 298 | if [ "$NODE_AVAILABLE" = "NO" ]; then 299 | echo "Installing Node.js via Homebrew..." 300 | brew install node 301 | NODE_AVAILABLE="YES" 302 | NODE_VERSION=$(node --version) 303 | echo "✅ Node.js installed: $NODE_VERSION" 304 | else 305 | echo "✅ Node.js already available: $NODE_VERSION" 306 | fi 307 | 308 | # Install Bun if not available 309 | if [ "$BUN_AVAILABLE" = "NO" ]; then 310 | echo "Installing Bun via Homebrew..." 311 | brew install bun 312 | BUN_AVAILABLE="YES" 313 | BUN_VERSION=$(bun --version) 314 | echo "✅ Bun installed: $BUN_VERSION" 315 | else 316 | echo "✅ Bun already available: $BUN_VERSION" 317 | fi 318 | 319 | # Verify installations 320 | echo "=== DEPENDENCY VERIFICATION ===" 321 | node --version && echo "✅ Node.js working" 322 | bun --version && echo "✅ Bun working" 323 | git --version && echo "✅ Git working" 324 | which cursor && echo "✅ Cursor available" || echo "❌ Install Cursor IDE manually" 325 | ``` 326 | 327 | **CHECKPOINT A1.1**: All dependencies must show ✅ before proceeding. 328 | 329 | **AI AGENTS**: Update progress after dependency installation: 330 | 331 | ``` 332 | CHECKPOINT 2: DEPENDENCIES INSTALLED 333 | 334 | Update Method A1 progress - mark Homebrew Check and Dependencies as completed: 335 | 336 | ```mermaid 337 | graph TD 338 | A[🔍 Environment Detection] --> B[📦 Method A1 Selected] 339 | B --> C[🍺 Homebrew Check] 340 | C --> D[📦 Install Dependencies] 341 | D --> E[⚙️ MCP Setup] 342 | E --> F[🚀 Ready for WebSocket] 343 | 344 | class A,B,C,D completed 345 | class E current 346 | class F pending 347 | 348 | classDef completed fill:#22c55e,stroke:#16a34a,stroke-width:2px,color:#fff 349 | classDef current fill:#3b82f6,stroke:#2563eb,stroke-width:2px,color:#fff 350 | classDef pending fill:#6b7280,stroke:#4b5563,stroke-width:2px,color:#fff 351 | ``` 352 | ``` 353 | 354 | ### Step A1.2: MCP Setup via Homebrew Method 355 | 356 | **🎯 Project is already cloned and open in Cursor IDE** 357 | 358 | 1. **Verify project structure**: 359 | 360 | ```bash 361 | # Verify current directory (should already be in cursor-talk-to-figma-mcp) 362 | pwd | grep "cursor-talk-to-figma-mcp" && echo "✅ Correct directory" || echo "❌ ERROR: Wrong directory" 363 | 364 | # Verify project structure 365 | ls package.json src && echo "✅ Project structure valid" || echo "❌ ERROR: Invalid project structure" 366 | 367 | # Install dependencies and setup 368 | bun install 369 | bun run build 370 | 371 | # Run setup (this configures MCP automatically) 372 | bun setup && echo "✅ Setup complete" || echo "❌ ERROR: Setup failed" 373 | 374 | # Verify MCP config was created 375 | test -f .cursor/mcp.json && echo "✅ MCP config created" || echo "❌ ERROR: MCP config missing" 376 | ``` 377 | 378 | **AI AGENTS**: Update progress after Method A1 completion: 379 | 380 | ``` 381 | CHECKPOINT 3: METHOD A1 COMPLETE 382 | 383 | Method A1 fully completed, ready for common steps: 384 | 385 | ```mermaid 386 | graph TD 387 | A[🔍 Environment Detection] --> B[📦 Method A1 Selected] 388 | B --> C[🍺 Homebrew Check] 389 | C --> D[📦 Install Dependencies] 390 | D --> E[⚙️ MCP Setup] 391 | E --> F[🚀 Ready for WebSocket] 392 | 393 | class A,B,C,D,E,F completed 394 | 395 | classDef completed fill:#22c55e,stroke:#16a34a,stroke-width:2px,color:#fff 396 | ``` 397 | 398 | Progress: Method A1 100% Complete ✅ - Proceeding to Common Steps 399 | ``` 400 | 401 | **Continue to Common Steps below.** 402 | 403 | --- 404 | 405 | ## 🔧 Method A2: Direct Installation 406 | 407 | ### 📊 Progress Tracking for Method A2 408 | 409 | **AI AGENTS**: Start by creating Method A2 progress diagram: 410 | 411 | ``` 412 | CHECKPOINT 1: STARTING METHOD A2 - DIRECT INSTALLATION 413 | 414 | Please create the Method A2 progress tracking diagram: 415 | 416 | ```mermaid 417 | graph TD 418 | A[🔍 Environment Detection] --> B[📦 Method A2 Selected] 419 | B --> C[⚡ Install Bun Direct] 420 | C --> D[⚙️ MCP Setup] 421 | D --> E[🚀 Ready for WebSocket] 422 | 423 | class A completed 424 | class B current 425 | class C,D,E pending 426 | 427 | classDef completed fill:#22c55e,stroke:#16a34a,stroke-width:2px,color:#fff 428 | classDef current fill:#3b82f6,stroke:#2563eb,stroke-width:2px,color:#fff 429 | classDef pending fill:#6b7280,stroke:#4b5563,stroke-width:2px,color:#fff 430 | ``` 431 | ``` 432 | 433 | ### Prerequisites Check for Method A2 434 | ```bash 435 | # Verify we have sudo access but no Homebrew 436 | [ "$SUDO_AVAILABLE" = "YES" ] && echo "✅ Sudo access confirmed" || exit 1 437 | [ "$HOMEBREW_AVAILABLE" = "NO" ] && echo "✅ Direct installation path confirmed" || echo "⚠️ Homebrew available, consider Method A1" 438 | ``` 439 | 440 | ### Step A2.1: Install Bun Directly 441 | 442 | ```bash 443 | # Install Bun directly (requires internet access) 444 | curl -fsSL https://bun.sh/install | bash 445 | 446 | # Reload shell environment 447 | if [[ "$SHELL" == *"zsh"* ]]; then 448 | source ~/.zshrc 2>/dev/null || echo "⚠️ No .zshrc found" 449 | echo "🐚 Using Zsh shell" 450 | elif [[ "$SHELL" == *"bash"* ]]; then 451 | source ~/.bashrc 2>/dev/null || source ~/.bash_profile 2>/dev/null || echo "⚠️ No .bashrc or .bash_profile found" 452 | echo "🐚 Using Bash shell" 453 | fi 454 | 455 | # Verify installation 456 | bun --version && echo "✅ Bun installed successfully" || echo "❌ ERROR: Bun installation failed" 457 | BUN_AVAILABLE="YES" 458 | BUN_VERSION=$(bun --version) 459 | ``` 460 | 461 | **AI AGENTS**: Update progress after Bun installation: 462 | 463 | ``` 464 | CHECKPOINT 2: BUN INSTALLED DIRECTLY 465 | 466 | Update Method A2 progress - Bun installation completed: 467 | 468 | ```mermaid 469 | graph TD 470 | A[🔍 Environment Detection] --> B[📦 Method A2 Selected] 471 | B --> C[⚡ Install Bun Direct] 472 | C --> D[⚙️ MCP Setup] 473 | D --> E[🚀 Ready for WebSocket] 474 | 475 | class A,B,C completed 476 | class D current 477 | class E pending 478 | 479 | classDef completed fill:#22c55e,stroke:#16a34a,stroke-width:2px,color:#fff 480 | classDef current fill:#3b82f6,stroke:#2563eb,stroke-width:2px,color:#fff 481 | classDef pending fill:#6b7280,stroke:#4b5563,stroke-width:2px,color:#fff 482 | ``` 483 | ``` 484 | 485 | ### Step A2.2: MCP Setup via Direct Method 486 | 487 | **🎯 Project is already cloned and open in Cursor IDE** 488 | 489 | 1. **Verify project setup**: 490 | 491 | ```bash 492 | # Verify current directory (should already be in cursor-talk-to-figma-mcp) 493 | pwd | grep "cursor-talk-to-figma-mcp" && echo "✅ Correct directory" || echo "❌ ERROR: Wrong directory" 494 | 495 | # Verify and setup 496 | ls package.json src && echo "✅ Project structure valid" || echo "❌ ERROR: Invalid project structure" 497 | bun install 498 | bun run build 499 | bun setup && echo "✅ Setup complete" || echo "❌ ERROR: Setup failed" 500 | test -f .cursor/mcp.json && echo "✅ MCP config created" || echo "❌ ERROR: MCP config missing" 501 | ``` 502 | 503 | **AI AGENTS**: Update progress after Method A2 completion: 504 | 505 | ``` 506 | CHECKPOINT 3: METHOD A2 COMPLETE 507 | 508 | Method A2 fully completed, ready for common steps: 509 | 510 | ```mermaid 511 | graph TD 512 | A[🔍 Environment Detection] --> B[📦 Method A2 Selected] 513 | B --> C[⚡ Install Bun Direct] 514 | C --> D[⚙️ MCP Setup] 515 | D --> E[🚀 Ready for WebSocket] 516 | 517 | class A,B,C,D,E completed 518 | 519 | classDef completed fill:#22c55e,stroke:#16a34a,stroke-width:2px,color:#fff 520 | ``` 521 | 522 | Progress: Method A2 100% Complete ✅ - Proceeding to Common Steps 523 | ``` 524 | 525 | **Continue to Common Steps below.** 526 | 527 | --- 528 | 529 | ## 📱 Method B: Cursor-Only Setup 530 | 531 | ### 📊 Progress Tracking for Method B 532 | 533 | **AI AGENTS**: Start by creating Method B progress diagram: 534 | 535 | ``` 536 | CHECKPOINT 1: STARTING METHOD B - CURSOR-ONLY SETUP 537 | 538 | Please create the Method B progress tracking diagram: 539 | 540 | ```mermaid 541 | graph TD 542 | A[🔍 Environment Detection] --> B[📦 Method B Selected] 543 | B --> C[🖥️ Project Already Open] 544 | C --> D[📖 Setup Dependencies] 545 | D --> E[⚙️ MCP Setup] 546 | E --> F[🚀 Ready for WebSocket] 547 | 548 | class A completed 549 | class B current 550 | class C,D,E,F pending 551 | 552 | classDef completed fill:#22c55e,stroke:#16a34a,stroke-width:2px,color:#fff 553 | classDef current fill:#3b82f6,stroke:#2563eb,stroke-width:2px,color:#fff 554 | classDef pending fill:#6b7280,stroke:#4b5563,stroke-width:2px,color:#fff 555 | ``` 556 | ``` 557 | 558 | ### Prerequisites Check for Method B 559 | ```bash 560 | # Verify Cursor-only setup conditions 561 | [ "$CURSOR_AVAILABLE" = "YES" ] && echo "✅ Cursor IDE available" || echo "❌ ERROR: Install Cursor IDE first" 562 | [ "$SUDO_AVAILABLE" = "NO" ] && echo "✅ Restricted environment confirmed" || echo "⚠️ Admin access available, consider Method A" 563 | ``` 564 | 565 | ### Step B.1: Verify Project is Open in Cursor 566 | 567 | **🎯 Project is already cloned and open in Cursor IDE** 568 | 569 | 1. **Verify project is properly loaded**: 570 | - Confirm Cursor IDE has the project open 571 | - Open terminal in Cursor: `Terminal > New Terminal` (or `Ctrl+Shift+`) 572 | 573 | ### Step B.2: Setup Dependencies in Cursor 574 | 575 | ```bash 576 | # Verify current directory (should already be in cursor-talk-to-figma-mcp) 577 | pwd | grep "cursor-talk-to-figma-mcp" && echo "✅ Correct directory" || echo "❌ ERROR: Wrong directory" 578 | 579 | # Verify project structure 580 | ls package.json src && echo "✅ Project structure valid" || echo "❌ ERROR: Invalid project structure" 581 | ``` 582 | 583 | **AI AGENTS**: Update progress after project cloning: 584 | 585 | ``` 586 | CHECKPOINT 2: PROJECT CLONED IN CURSOR 587 | 588 | Update Method B progress - project cloned successfully: 589 | 590 | ```mermaid 591 | graph TD 592 | A[🔍 Environment Detection] --> B[📦 Method B Selected] 593 | B --> C[🖥️ Project Already Open] 594 | C --> D[📖 Setup Dependencies] 595 | D --> E[⚙️ MCP Setup] 596 | E --> F[🚀 Ready for WebSocket] 597 | 598 | class A,B,C,D completed 599 | class E current 600 | class F pending 601 | 602 | classDef completed fill:#22c55e,stroke:#16a34a,stroke-width:2px,color:#fff 603 | classDef current fill:#3b82f6,stroke:#2563eb,stroke-width:2px,color:#fff 604 | classDef pending fill:#6b7280,stroke:#4b5563,stroke-width:2px,color:#fff 605 | ``` 606 | ``` 607 | 608 | ### Step B.3: Follow README Setup in Cursor 609 | 610 | **📖 CRITICAL FOR AI AGENTS**: Ask the AI assistant in Cursor to read and execute the README: 611 | 612 | ``` 613 | Please read the @readme.md file and help me set up this project by following the installation instructions step by step. 614 | 615 | IMPORTANT: For each step in the setup process, please: 616 | 1. Create a Mermaid flowchart showing the complete installation process 617 | 2. Highlight which step we're currently on 618 | 3. Show what steps are completed, current, and upcoming 619 | 4. Include brief descriptions of what each step does 620 | 5. Update the diagram as we progress through each step 621 | 622 | Use this Mermaid template structure: 623 | - Use different colors/styles to show: ✅ Completed, 🔄 Current, ⏳ Upcoming 624 | - Include step numbers and brief descriptions 625 | - Show the flow from Prerequisites → Installation → Configuration → Testing 626 | - Make it visually clear where we are in the process 627 | 628 | This will help track overall progress and determine next steps at each stage. 629 | ``` 630 | 631 | **AI AGENTS**: Update progress after Method B completion: 632 | 633 | ``` 634 | CHECKPOINT 3: METHOD B COMPLETE 635 | 636 | Method B fully completed, ready for common steps: 637 | 638 | ```mermaid 639 | graph TD 640 | A[🔍 Environment Detection] --> B[📦 Method B Selected] 641 | B --> C[🖥️ Project Already Open] 642 | C --> D[📖 Setup Dependencies] 643 | D --> E[⚙️ MCP Setup] 644 | E --> F[🚀 Ready for WebSocket] 645 | 646 | class A,B,C,D,E,F completed 647 | 648 | classDef completed fill:#22c55e,stroke:#16a34a,stroke-width:2px,color:#fff 649 | ``` 650 | 651 | Progress: Method B 100% Complete ✅ - Proceeding to Common Steps 652 | ``` 653 | 654 | **Continue to Common Steps below.** 655 | 656 | --- 657 | 658 | ## 🔗 Common Steps (All Methods) 659 | 660 | ### Step C.1: Verify MCP Configuration 661 | 662 | **🚨 CRITICAL FOR AI AGENTS**: Project is already cloned and open in Cursor. 663 | 664 | ```bash 665 | # Verify current directory (should already be in cursor-talk-to-figma-mcp) 666 | pwd | grep "cursor-talk-to-figma-mcp" && echo "✅ Correct directory" || echo "❌ ERROR: Wrong directory" 667 | 668 | # Verify project structure 669 | ls package.json src && echo "✅ Project structure valid" || echo "❌ ERROR: Invalid project structure" 670 | 671 | # Verify MCP config exists (created by bun setup) 672 | if [ -f ".cursor/mcp.json" ]; then 673 | echo "✅ MCP config found" 674 | else 675 | echo "⚠️ MCP config not found - running setup..." 676 | bun setup 677 | fi 678 | 679 | echo "✅ Project verified - MCP should be configured" 680 | ``` 681 | 682 | **CHECKPOINT C.1**: Verify in Cursor: 683 | 1. Open Settings (`Cmd+,` on Mac, `Ctrl+,` on Windows/Linux) 684 | 2. Search "MCP" 685 | 3. Confirm "TalkToFigma" shows as "Connected" 686 | 687 | **AI AGENTS**: Update overall progress after Cursor launch: 688 | 689 | ``` 690 | CHECKPOINT 3: MCP SETUP AND CONFIGURATION COMPLETE 691 | 692 | Update overall progress - MCP setup and configured: 693 | 694 | ```mermaid 695 | graph TD 696 | A[🔍 Environment Detection] --> B[📦 Method Selection] 697 | B --> C[⚡ Dependency Installation] 698 | C --> D[⚙️ MCP setup and configuration] 699 | D --> E[🌐 WebSocket Server] 700 | E --> F[🔌 Figma Plugin] 701 | F --> G[🧪 Integration Test] 702 | G --> H[✅ Setup Complete] 703 | 704 | class A,B,C,D completed 705 | class E current 706 | class F,G,H pending 707 | 708 | classDef completed fill:#22c55e,stroke:#16a34a,stroke-width:2px,color:#fff 709 | classDef current fill:#3b82f6,stroke:#2563eb,stroke-width:2px,color:#fff 710 | classDef pending fill:#6b7280,stroke:#4b5563,stroke-width:2px,color:#fff 711 | ``` 712 | 713 | Progress: 50% Complete - MCP Configured ✅ - Starting WebSocket Server 714 | ``` 715 | 716 | ### Step C.2: Start WebSocket Server 717 | 718 | To ensure stability and easy monitoring, we will run the WebSocket server in the foreground. This is the recommended method as it dedicates a terminal to the server, confirming it's always running, and saves all logs to a file for later review. 719 | 720 | **How to Start and Monitor the Server:** 721 | 722 | 1. **Start the Server**: Open a new terminal in Cursor and run the following command. The terminal will become unresponsive, which means the server is running correctly. 723 | ```bash 724 | # Runs the server in the foreground and saves all logs to websocket.log 725 | bun socket > websocket.log 2>&1 726 | ``` 727 | 728 | 2. **Monitor Logs**: To see server activity, open a *second* terminal and use this command for a live feed of the logs: 729 | ```bash 730 | tail -f websocket.log 731 | ``` 732 | You can also simply open the `websocket.log` file to view its contents. 733 | 734 | 3. **Stop the Server**: To stop the server, go to its dedicated terminal window and press `Ctrl + C`. 735 | 736 | **CHECKPOINT C.2**: The server terminal should be active (and unresponsive). You should see a "WebSocket server running on port 3055" message at the beginning of the `websocket.log` file. **Keep the server terminal open.** 737 | 738 | ### 🔍 Verify WebSocket Server Status 739 | 740 | **🚨 CRITICAL FOR AI AGENTS**: Use Cursor's `@Terminals` to monitor WebSocket server status: 741 | 742 | 1. **Check Active Terminals**: 743 | - In Cursor, type `@Terminals` in chat 744 | - This will show all currently running terminal processes 745 | - Verify you can see the WebSocket server terminal running 746 | - Confirm the server shows "WebSocket server running on port 3055" message 747 | 748 | 2. **Verify Server is Active**: 749 | ```bash 750 | # In a new terminal (don't close the WebSocket terminal!) 751 | curl -I http://localhost:3055 2>/dev/null && echo "✅ WebSocket server responding" || echo "❌ Server not responding" 752 | 753 | # Check port status 754 | lsof -i :3055 && echo "✅ Port 3055 in use by WebSocket server" || echo "❌ Port 3055 not in use" 755 | ``` 756 | 757 | 3. **Monitor Server Logs**: 758 | - Keep the WebSocket terminal visible in Cursor 759 | - Watch for connection messages when Figma plugin connects 760 | - Server logs will show plugin connection status in real-time 761 | 762 | **⚠️ IMPORTANT**: Do NOT close the WebSocket terminal - it must stay running throughout the entire Figma integration session. 763 | 764 | **AI AGENTS**: Update progress after WebSocket server start: 765 | 766 | ``` 767 | CHECKPOINT 4: WEBSOCKET SERVER RUNNING 768 | 769 | Update progress - WebSocket server successfully started: 770 | 771 | ```mermaid 772 | graph TD 773 | A[🔍 Environment Detection] --> B[📦 Method Selection] 774 | B --> C[⚡ Dependency Installation] 775 | C --> D[⚙️ MCP setup and configuration] 776 | D --> E[🌐 WebSocket Server] 777 | E --> F[🔌 Figma Plugin] 778 | F --> G[🧪 Integration Test] 779 | G --> H[✅ Setup Complete] 780 | 781 | class A,B,C,D,E completed 782 | class F current 783 | class G,H pending 784 | 785 | classDef completed fill:#22c55e,stroke:#16a34a,stroke-width:2px,color:#fff 786 | classDef current fill:#3b82f6,stroke:#2563eb,stroke-width:2px,color:#fff 787 | classDef pending fill:#6b7280,stroke:#4b5563,stroke-width:2px,color:#fff 788 | ``` 789 | 790 | Progress: 63% Complete - WebSocket Running ✅ - Installing Figma Plugin 791 | ``` 792 | 793 | ### Step C.3: Install and Configure Figma Plugin 794 | 795 | #### 🔌 Install the Community Plugin 796 | 797 | 1. **Open the Plugin Page**: 798 | - Navigate to: https://www.figma.com/community/plugin/1485687494525374295/cursor-talk-to-figma-mcp-plugin 799 | - Click **"Install"** to add the plugin to your Figma account 800 | 801 | 2. **Open Figma and Run the Plugin**: 802 | - Open any Figma file (or create a new one) 803 | - Go to `Plugins` menu → `Cursor Talk to Figma MCP Plugin` 804 | - The plugin panel will open 805 | 806 | #### ⚙️ Configure Plugin to Connect to Local WebSocket 807 | 808 | **🚨 CRITICAL**: The plugin needs to connect to your local WebSocket server: 809 | 810 | 1. **In the Plugin Panel**: 811 | - Look for **"WebSocket URL"** or **"Server URL"** setting 812 | - Change the URL from default to: `ws://localhost:3055` 813 | - Click **"Connect"** or **"Save"** to apply the setting 814 | 815 | 2. **Verify Connection**: 816 | - The plugin should show **"Connected"** status 817 | - You should see green indicator or success message in the plugin 818 | 819 | #### 📡 Monitor Connection Status Using @Terminals 820 | 821 | **🔍 IMPORTANT FOR AI AGENTS**: Use Cursor's `@Terminals` to verify the plugin connection: 822 | 823 | 1. **Check WebSocket Server Logs**: 824 | - In Cursor, type `@Terminals` in chat 825 | - Look at the WebSocket server terminal 826 | - When plugin connects successfully, you should see log messages like: 827 | ``` 828 | ✅ New WebSocket connection from Figma plugin 829 | ✅ Plugin connected successfully 830 | ✅ Handshake completed 831 | ``` 832 | 833 | 2. **Connection Troubleshooting**: 834 | - If no connection messages appear in server logs, the plugin is not connecting properly 835 | - Check that WebSocket URL in plugin is set to `ws://localhost:3055` 836 | - Verify the WebSocket server is still running (check `@Terminals`) 837 | - Try refreshing the Figma page and reconnecting the plugin 838 | 839 | **AI AGENTS**: Update progress after Figma plugin installation: 840 | 841 | ``` 842 | CHECKPOINT 5: FIGMA PLUGIN INSTALLED 843 | 844 | Update progress - Figma plugin successfully installed: 845 | 846 | ```mermaid 847 | graph TD 848 | A[🔍 Environment Detection] --> B[📦 Method Selection] 849 | B --> C[⚡ Dependency Installation] 850 | C --> D[⚙️ MCP setup and configuration] 851 | D --> E[🌐 WebSocket Server] 852 | E --> F[🔌 Figma Plugin] 853 | F --> G[🧪 Integration Test] 854 | G --> H[✅ Setup Complete] 855 | 856 | class A,B,C,D,E,F completed 857 | class G current 858 | class H pending 859 | 860 | classDef completed fill:#22c55e,stroke:#16a34a,stroke-width:2px,color:#fff 861 | classDef current fill:#3b82f6,stroke:#2563eb,stroke-width:2px,color:#fff 862 | classDef pending fill:#6b7280,stroke:#4b5563,stroke-width:2px,color:#fff 863 | ``` 864 | 865 | Progress: 75% Complete - Plugin Installed ✅ - Running Integration Tests 866 | ``` 867 | 868 | ### Step C.4: Test Integration 869 | 870 | #### 🧪 Test MCP Commands in Cursor 871 | 872 | **In Cursor (where MCP is connected):** 873 | 874 | ```bash 875 | # Test 1: MCP Connection 876 | join_channel 877 | # Expected: "Successfully joined channel" message 878 | 879 | # Test 2: Figma Communication 880 | get_document_info 881 | # Expected: JSON data from Figma document 882 | ``` 883 | 884 | #### 📋 Verify Complete Integration Using @Terminals 885 | 886 | **🔍 FINAL VERIFICATION FOR AI AGENTS**: 887 | 888 | 1. **Check All Active Processes**: 889 | - In Cursor, type `@Terminals` in chat 890 | - Verify you can see: 891 | - ✅ **WebSocket server terminal** still running with "WebSocket server running on port 3055" 892 | - ✅ **Connection logs** showing Figma plugin is connected 893 | - ✅ **MCP status** showing TalkToFigma as connected 894 | 895 | 2. **Test End-to-End Communication**: 896 | ```bash 897 | # In Cursor chat, test these MCP commands: 898 | get_document_info 899 | get_selection 900 | ``` 901 | - Commands should return JSON data from your Figma document 902 | - Check `@Terminals` - WebSocket logs should show command activity 903 | - Figma plugin should show activity/response indicators 904 | 905 | 3. **Integration Status Checklist**: 906 | - ✅ WebSocket server running (visible in `@Terminals`) 907 | - ✅ Figma plugin connected (connection logs in server terminal) 908 | - ✅ MCP commands working (returns Figma data) 909 | - ✅ No error messages in any terminals 910 | 911 | **CHECKPOINT C.4**: All tests must pass and `@Terminals` should show healthy connections for successful setup. 912 | 913 | **AI AGENTS**: Update progress after successful integration test: 914 | 915 | ``` 916 | CHECKPOINT 6: SETUP COMPLETE - INTEGRATION SUCCESSFUL 917 | 918 | 🎉 FINAL STATUS - All components working perfectly: 919 | 920 | ```mermaid 921 | graph TD 922 | A[🔍 Environment Detection] --> B[📦 Method Selection] 923 | B --> C[⚡ Dependency Installation] 924 | C --> D[⚙️ MCP setup and configuration] 925 | D --> E[🌐 WebSocket Server] 926 | E --> F[🔌 Figma Plugin] 927 | F --> G[🧪 Integration Test] 928 | G --> H[✅ Setup Complete] 929 | 930 | class A,B,C,D,E,F,G,H completed 931 | 932 | classDef completed fill:#22c55e,stroke:#16a34a,stroke-width:2px,color:#fff 933 | ``` 934 | 935 | 🎯 **FINAL RESULT**: 100% Complete ✅ 936 | - ✅ Environment Detection Complete 937 | - ✅ Method Successfully Executed 938 | - ✅ Dependencies Installed 939 | - ✅ Project Cloned & Configured 940 | - ✅ MCP Connected 941 | - ✅ WebSocket Server Running 942 | - ✅ Figma Plugin Active 943 | - ✅ Integration Tests Passed 944 | - ✅ **READY TO USE!** 945 | 946 | **🚀 AI Agent can now assist with Figma design tasks through Cursor!** 947 | ``` 948 | 949 | --- 950 | 951 | ## 🔍 AI Agent Troubleshooting Decision Tree 952 | 953 | ### Issue: Prerequisites Failed 954 | ```bash 955 | # Decision tree for prerequisite failures 956 | if [ "$CURSOR_AVAILABLE" = "NO" ]; then 957 | echo "❌ CRITICAL: Install Cursor IDE first - https://cursor.sh/" 958 | exit 1 959 | elif [ "$GIT_AVAILABLE" = "NO" ]; then 960 | echo "❌ CRITICAL: Install Git first" 961 | exit 1 962 | else 963 | echo "✅ Prerequisites satisfied, continue setup" 964 | fi 965 | ``` 966 | 967 | ### Issue: MCP Not Connected 968 | ```bash 969 | # AI Agent debugging steps 970 | echo "🔍 Debugging MCP connection..." 971 | 972 | # Check if in correct project directory 973 | if [ ! -f "package.json" ] || [ ! -d "src" ]; then 974 | echo "❌ Not in project directory" 975 | echo "💡 Please ensure you're in the cloned cursor-talk-to-figma-mcp directory" 976 | echo "💡 Use Cursor's File > Open Folder to open the cloned project" 977 | exit 1 978 | fi 979 | 980 | # Check if MCP config exists 981 | if [ ! -f ".cursor/mcp.json" ]; then 982 | echo "⚠️ MCP config missing - running setup..." 983 | bun setup 984 | fi 985 | 986 | # Restart Cursor if needed 987 | echo "💡 Restarting Cursor to reload MCP configuration..." 988 | cursor . 989 | sleep 5 990 | ``` 991 | 992 | ### Issue: WebSocket Connection Failed 993 | ```bash 994 | # AI Agent network debugging 995 | echo "🔍 Debugging WebSocket connection..." 996 | 997 | # Step 1: Use @Terminals to check current state 998 | echo "💡 First, type '@Terminals' in Cursor chat to see all running processes" 999 | echo "💡 Look for WebSocket server terminal and check its status" 1000 | 1001 | # Check if port is in use 1002 | if lsof -i :3055 >/dev/null 2>&1; then 1003 | echo "⚠️ Port 3055 in use, killing existing process" 1004 | lsof -ti:3055 | xargs kill -9 2>/dev/null || true 1005 | sleep 2 1006 | fi 1007 | 1008 | # Restart WebSocket server based on available runtime 1009 | if [ "$BUN_AVAILABLE" = "YES" ]; then 1010 | echo "🚀 Starting WebSocket server with Bun..." 1011 | bun socket 1012 | elif [ "$NODE_AVAILABLE" = "YES" ]; then 1013 | echo "🚀 Starting WebSocket server with Node..." 1014 | npm run socket || npx bun socket 1015 | else 1016 | echo "❌ No suitable runtime for WebSocket server" 1017 | exit 1 1018 | fi 1019 | 1020 | echo "💡 After starting server, use '@Terminals' again to verify it's running" 1021 | echo "💡 Look for 'WebSocket server running on port 3055' message" 1022 | ``` 1023 | 1024 | ### Issue: Figma Plugin Not Connecting 1025 | ```bash 1026 | # AI Agent plugin debugging 1027 | echo "🔍 Debugging Figma plugin connection..." 1028 | 1029 | echo "💡 Use '@Terminals' in Cursor to check WebSocket server logs" 1030 | echo "💡 You should see connection attempts from Figma plugin" 1031 | 1032 | # Common plugin connection issues: 1033 | echo "🔧 Plugin Connection Checklist:" 1034 | echo "1. Plugin WebSocket URL set to 'ws://localhost:3055'" 1035 | echo "2. WebSocket server running (check '@Terminals')" 1036 | echo "3. Figma plugin installed and activated" 1037 | echo "4. No firewall blocking localhost:3055" 1038 | 1039 | echo "💡 In Figma plugin panel:" 1040 | echo "- Verify connection URL is 'ws://localhost:3055'" 1041 | echo "- Click disconnect/reconnect if needed" 1042 | echo "- Check for error messages in plugin" 1043 | 1044 | echo "💡 Monitor '@Terminals' for real-time connection logs" 1045 | ``` 1046 | 1047 | ### Issue: Runtime Not Found 1048 | ```bash 1049 | # AI Agent runtime fallback logic 1050 | echo "🔍 Attempting runtime fallback..." 1051 | 1052 | if [ "$HOMEBREW_AVAILABLE" = "YES" ] && [ "$SUDO_AVAILABLE" = "YES" ]; then 1053 | echo "Installing missing runtime via Homebrew..." 1054 | brew install bun node 1055 | elif [ "$SUDO_AVAILABLE" = "YES" ]; then 1056 | echo "Installing Bun directly..." 1057 | curl -fsSL https://bun.sh/install | bash 1058 | source ~/.zshrc 2>/dev/null || source ~/.bashrc 2>/dev/null || true 1059 | else 1060 | echo "❌ Cannot install runtime in restricted environment" 1061 | echo "💡 Try Method B: Cursor-Only Setup" 1062 | exit 1 1063 | fi 1064 | ``` 1065 | 1066 | --- 1067 | 1068 | ## ✅ AI Agent Success Verification Matrix 1069 | 1070 | **AI Agents should verify ALL conditions before marking setup as complete:** 1071 | 1072 | ### Environment Verification 1073 | ```bash 1074 | echo "=== FINAL VERIFICATION MATRIX ===" 1075 | 1076 | # System Environment 1077 | [ "$OS_TYPE" != "" ] && echo "✅ OS Detection: $OS_TYPE" || echo "❌ OS Detection Failed" 1078 | 1079 | # Required Tools 1080 | [ "$CURSOR_AVAILABLE" = "YES" ] && echo "✅ Cursor IDE Available" || echo "❌ Cursor Missing" 1081 | [ "$GIT_AVAILABLE" = "YES" ] && echo "✅ Git Available" || echo "❌ Git Missing" 1082 | 1083 | # Runtime Environment 1084 | if [ "$BUN_AVAILABLE" = "YES" ]; then 1085 | echo "✅ Bun Runtime: $BUN_VERSION" 1086 | elif [ "$NODE_AVAILABLE" = "YES" ]; then 1087 | echo "✅ Node.js Runtime: $NODE_VERSION" 1088 | else 1089 | echo "❌ No Suitable Runtime Found" 1090 | fi 1091 | 1092 | # Project Setup 1093 | [ -f ".cursor/mcp.json" ] && echo "✅ MCP Config Present" || echo "❌ MCP Config Missing" 1094 | [ -f "package.json" ] && echo "✅ Project Structure Valid" || echo "❌ Invalid Project" 1095 | 1096 | # Process Status 1097 | if lsof -i :3055 >/dev/null 2>&1; then 1098 | echo "✅ WebSocket Server Running (Port 3055)" 1099 | else 1100 | echo "❌ WebSocket Server Not Running" 1101 | fi 1102 | ``` 1103 | 1104 | ### Functional Verification 1105 | **Execute in Cursor with MCP connected:** 1106 | 1107 | 1. **MCP Connection Test**: `join_channel` → Should return success message 1108 | 2. **Figma Communication Test**: `get_document_info` → Should return JSON data 1109 | 3. **Plugin Status**: Figma plugin should show as connected 1110 | 1111 | ### 🎉 Success Criteria 1112 | **ALL of the following must be true:** 1113 | - ✅ Environment detection completed without errors 1114 | - ✅ Appropriate method selected and executed based on system capabilities 1115 | - ✅ Project successfully cloned and configured 1116 | - ✅ MCP configuration file present and valid 1117 | - ✅ Cursor IDE launched from project directory 1118 | - ✅ MCP shows "TalkToFigma" as "Connected" in settings 1119 | - ✅ WebSocket server running on port 3055 (verified via `@Terminals`) 1120 | - ✅ Figma plugin installed, configured with `ws://localhost:3055`, and connected 1121 | - ✅ Plugin connection visible in WebSocket server logs (check `@Terminals`) 1122 | - ✅ `join_channel` command works in Cursor 1123 | - ✅ `get_document_info` returns Figma document data 1124 | - ✅ All processes healthy and running (verified via `@Terminals`) 1125 | 1126 | **If any criterion fails, AI agents should follow the troubleshooting decision tree above.** 1127 | 1128 | --- 1129 | 1130 | ## 🚀 AI Agent Quick Commands 1131 | 1132 | ### Recommended Setup (Use Cursor's Built-in Git Clone) 1133 | **AI AGENTS**: Instead of terminal commands, guide users to: 1134 | 1135 | 1. **Open Cursor IDE** 1136 | 2. **Clone Repository**: Use `Cmd+Shift+P` → "Git: Clone" 1137 | 3. **Enter URL**: `https://github.com/sonnylazuardi/cursor-talk-to-figma-mcp.git` 1138 | 4. **Select location and clone** 1139 | 5. **Open cloned project in Cursor** 1140 | 6. **Run setup**: `bun setup` 1141 | 1142 | ### Alternative One-Line Commands (For Advanced Users) 1143 | ```bash 1144 | # Method A1 (Homebrew) - Clone and auto-open in Cursor 1145 | [ "$HOMEBREW_AVAILABLE" = "YES" ] && cd ~/Desktop && git clone https://github.com/sonnylazuardi/cursor-talk-to-figma-mcp.git && cd cursor-talk-to-figma-mcp && brew install bun && bun setup && cursor . 1146 | 1147 | # Method A2 (Direct) - Clone and auto-open in Cursor 1148 | [ "$SUDO_AVAILABLE" = "YES" ] && cd ~/Desktop && git clone https://github.com/sonnylazuardi/cursor-talk-to-figma-mcp.git && cd cursor-talk-to-figma-mcp && curl -fsSL https://bun.sh/install | bash && source ~/.zshrc && bun setup && cursor . 1149 | 1150 | # Method B (Cursor-only) - Clone and open manually 1151 | [ "$CURSOR_AVAILABLE" = "YES" ] && cd ~/Desktop && git clone https://github.com/sonnylazuardi/cursor-talk-to-figma-mcp.git && echo "✅ Project cloned to ~/Desktop/cursor-talk-to-figma-mcp" && echo "💡 Open this folder in Cursor and run 'bun setup'" 1152 | ``` 1153 | 1154 | ### Service Management 1155 | ```bash 1156 | # Start WebSocket Server (background) 1157 | nohup bun socket > websocket.log 2>&1 & echo $! > websocket.pid 1158 | 1159 | # Stop WebSocket Server 1160 | [ -f websocket.pid ] && kill $(cat websocket.pid) && rm websocket.pid 1161 | 1162 | # Check Service Status 1163 | ps aux | grep -E "(bun socket|node.*socket)" || echo "WebSocket server not running" 1164 | ``` 1165 | 1166 | ### 📊 Monitor Services Using @Terminals 1167 | 1168 | **🔍 RECOMMENDED FOR AI AGENTS**: Use Cursor's `@Terminals` for real-time monitoring: 1169 | 1170 | 1. **Check Active Services**: 1171 | - Type `@Terminals` in Cursor chat anytime 1172 | - Instantly see all running terminal processes 1173 | - Verify WebSocket server status without additional commands 1174 | 1175 | 2. **Real-time Connection Monitoring**: 1176 | - Watch WebSocket server logs for Figma plugin connections 1177 | - See MCP command activity in real-time 1178 | - Monitor for errors or disconnections 1179 | 1180 | 3. **Quick Health Check**: 1181 | - `@Terminals` shows if WebSocket server is still running 1182 | - Displays connection status and recent activity 1183 | - No need for additional terminal commands 1184 | 1185 | **Remember**: Always keep the WebSocket server running for the Figma plugin to communicate with Cursor! Use `@Terminals` to monitor its health. ```