#
tokens: 29699/50000 15/17 files (page 1/3)
lines: on (toggle) GitHub
raw markdown copy reset
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. 
```
Page 1/3FirstPrevNextLast