#
tokens: 49217/50000 53/63 files (page 1/3)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 1 of 3. Use http://codebase.md/sichang824/mcp-figma?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .env.example
├── .envrc
├── .gitignore
├── .mcp.pid
├── bun.lockb
├── docs
│   ├── 01-overview.md
│   ├── 02-implementation-steps.md
│   ├── 03-components-and-features.md
│   ├── 04-usage-guide.md
│   ├── 05-project-status.md
│   ├── image.png
│   ├── README.md
│   └── widget-tools-guide.md
├── Makefile
├── manifest.json
├── package.json
├── prompt.md
├── README.md
├── README.zh.md
├── src
│   ├── config
│   │   └── env.ts
│   ├── index.ts
│   ├── plugin
│   │   ├── code.js
│   │   ├── code.ts
│   │   ├── creators
│   │   │   ├── componentCreators.ts
│   │   │   ├── containerCreators.ts
│   │   │   ├── elementCreator.ts
│   │   │   ├── imageCreators.ts
│   │   │   ├── shapeCreators.ts
│   │   │   ├── sliceCreators.ts
│   │   │   ├── specialCreators.ts
│   │   │   └── textCreator.ts
│   │   ├── manifest.json
│   │   ├── README.md
│   │   ├── tsconfig.json
│   │   ├── ui.html
│   │   └── utils
│   │       ├── colorUtils.ts
│   │       └── nodeUtils.ts
│   ├── resources.ts
│   ├── services
│   │   ├── figma-api.ts
│   │   ├── websocket.ts
│   │   └── widget-api.ts
│   ├── tools
│   │   ├── canvas.ts
│   │   ├── comment.ts
│   │   ├── component.ts
│   │   ├── file.ts
│   │   ├── frame.ts
│   │   ├── image.ts
│   │   ├── index.ts
│   │   ├── node.ts
│   │   ├── page.ts
│   │   ├── search.ts
│   │   ├── utils
│   │   │   └── widget-utils.ts
│   │   ├── version.ts
│   │   ├── widget
│   │   │   ├── analyze-widget-structure.ts
│   │   │   ├── get-widget-sync-data.ts
│   │   │   ├── get-widget.ts
│   │   │   ├── get-widgets.ts
│   │   │   ├── index.ts
│   │   │   ├── README.md
│   │   │   ├── search-widgets.ts
│   │   │   └── widget-tools.ts
│   │   └── zod-schemas.ts
│   ├── utils
│   │   ├── figma-utils.ts
│   │   └── widget-utils.ts
│   ├── utils.ts
│   ├── widget
│   │   └── utils
│   │       └── widget-tools.ts
│   └── widget-tools.ts
├── tsconfig.json
└── tsconfig.widget.json
```

# Files

--------------------------------------------------------------------------------
/.envrc:
--------------------------------------------------------------------------------

```
1 | dotenv
```

--------------------------------------------------------------------------------
/.mcp.pid:
--------------------------------------------------------------------------------

```
1 | 11949
2 | 
```

--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------

```
1 | # Figma API credentials
2 | FIGMA_PERSONAL_ACCESS_TOKEN=your_figma_token_here
3 | 
4 | # Server configuration
5 | PORT=3001
6 | NODE_ENV=development
7 | 
```

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
  1 | # Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
  2 | 
  3 | # Logs
  4 | 
  5 | logs
  6 | _.log
  7 | npm-debug.log_
  8 | yarn-debug.log*
  9 | yarn-error.log*
 10 | lerna-debug.log*
 11 | .pnpm-debug.log*
 12 | 
 13 | # Caches
 14 | 
 15 | .cache
 16 | 
 17 | # Diagnostic reports (https://nodejs.org/api/report.html)
 18 | 
 19 | report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
 20 | 
 21 | # Runtime data
 22 | 
 23 | pids
 24 | _.pid
 25 | _.seed
 26 | *.pid.lock
 27 | 
 28 | # Directory for instrumented libs generated by jscoverage/JSCover
 29 | 
 30 | lib-cov
 31 | 
 32 | # Coverage directory used by tools like istanbul
 33 | 
 34 | coverage
 35 | *.lcov
 36 | 
 37 | # nyc test coverage
 38 | 
 39 | .nyc_output
 40 | 
 41 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
 42 | 
 43 | .grunt
 44 | 
 45 | # Bower dependency directory (https://bower.io/)
 46 | 
 47 | bower_components
 48 | 
 49 | # node-waf configuration
 50 | 
 51 | .lock-wscript
 52 | 
 53 | # Compiled binary addons (https://nodejs.org/api/addons.html)
 54 | 
 55 | build/Release
 56 | 
 57 | # Dependency directories
 58 | 
 59 | node_modules/
 60 | jspm_packages/
 61 | 
 62 | # Snowpack dependency directory (https://snowpack.dev/)
 63 | 
 64 | web_modules/
 65 | 
 66 | # TypeScript cache
 67 | 
 68 | *.tsbuildinfo
 69 | 
 70 | # Optional npm cache directory
 71 | 
 72 | .npm
 73 | 
 74 | # Optional eslint cache
 75 | 
 76 | .eslintcache
 77 | 
 78 | # Optional stylelint cache
 79 | 
 80 | .stylelintcache
 81 | 
 82 | # Microbundle cache
 83 | 
 84 | .rpt2_cache/
 85 | .rts2_cache_cjs/
 86 | .rts2_cache_es/
 87 | .rts2_cache_umd/
 88 | 
 89 | # Optional REPL history
 90 | 
 91 | .node_repl_history
 92 | 
 93 | # Output of 'npm pack'
 94 | 
 95 | *.tgz
 96 | 
 97 | # Yarn Integrity file
 98 | 
 99 | .yarn-integrity
100 | 
101 | # dotenv environment variable files
102 | 
103 | .env
104 | .env.development.local
105 | .env.test.local
106 | .env.production.local
107 | .env.local
108 | 
109 | # parcel-bundler cache (https://parceljs.org/)
110 | 
111 | .parcel-cache
112 | 
113 | # Next.js build output
114 | 
115 | .next
116 | out
117 | 
118 | # Nuxt.js build / generate output
119 | 
120 | .nuxt
121 | dist
122 | 
123 | # Gatsby files
124 | 
125 | # Comment in the public line in if your project uses Gatsby and not Next.js
126 | 
127 | # https://nextjs.org/blog/next-9-1#public-directory-support
128 | 
129 | # public
130 | 
131 | # vuepress build output
132 | 
133 | .vuepress/dist
134 | 
135 | # vuepress v2.x temp and cache directory
136 | 
137 | .temp
138 | 
139 | # Docusaurus cache and generated files
140 | 
141 | .docusaurus
142 | 
143 | # Serverless directories
144 | 
145 | .serverless/
146 | 
147 | # FuseBox cache
148 | 
149 | .fusebox/
150 | 
151 | # DynamoDB Local files
152 | 
153 | .dynamodb/
154 | 
155 | # TernJS port file
156 | 
157 | .tern-port
158 | 
159 | # Stores VSCode versions used for testing VSCode extensions
160 | 
161 | .vscode-test
162 | 
163 | # yarn v2
164 | 
165 | .yarn/cache
166 | .yarn/unplugged
167 | .yarn/build-state.yml
168 | .yarn/install-state.gz
169 | .pnp.*
170 | 
171 | # IntelliJ based IDEs
172 | .idea
173 | 
174 | # Finder (MacOS) folder config
175 | .DS_Store
176 | .env.test
177 | 
178 | # Environment files
179 | .env.*
180 | !.env.example
181 | 
182 | # Dependencies
183 | node_modules/
184 | .npm
185 | npm-debug.log*
186 | yarn-debug.log*
187 | yarn-error.log*
188 | 
189 | # Build output
190 | dist/
191 | build/
192 | 
193 | # OS specific files
194 | .DS_Store
195 | Thumbs.db
196 | 
197 | # IDE specific files
198 | .idea/
199 | .vscode/
200 | *.sublime-*
201 | .history/
202 | 
```

--------------------------------------------------------------------------------
/src/tools/widget/README.md:
--------------------------------------------------------------------------------

```markdown
 1 | # Widget Tools for Figma MCP
 2 | 
 3 | This directory contains tools for interacting with Figma widgets through the MCP server.
 4 | 
 5 | ## Directory Structure
 6 | 
 7 | Each tool is organized in its own directory:
 8 | 
 9 | - `get-widgets`: Lists all widgets in a file
10 | - `get-widget`: Gets detailed information about a specific widget
11 | - `get-widget-sync-data`: Retrieves a widget's synchronized state data
12 | - `search-widgets`: Searches for widgets with specific properties
13 | - `analyze-widget-structure`: Provides detailed analysis of a widget's structure
14 | 
15 | ## Adding New Widget Tools
16 | 
17 | To add a new widget tool:
18 | 
19 | 1. Create a new directory for your tool under `src/tools/widget/`
20 | 2. Create an `index.ts` file with your tool implementation
21 | 3. Update the main `index.ts` to import and register your tool
22 | 
23 | ## Using Shared Utilities
24 | 
25 | Common widget utilities can be found in `src/tools/utils/widget-utils.ts`.
26 | 
27 | ## Widget Tool Pattern
28 | 
29 | Each widget tool follows this pattern:
30 | 
31 | ```typescript
32 | export const yourToolName = (server: McpServer) => {
33 |   server.tool(
34 |     "tool_name",
35 |     {
36 |       // Parameter schema using zod
37 |       param1: z.string().describe("Description"),
38 |       param2: z.number().describe("Description")
39 |     },
40 |     async ({ param1, param2 }) => {
41 |       try {
42 |         // Tool implementation
43 |         return {
44 |           content: [
45 |             { type: "text", text: "Response content" }
46 |           ]
47 |         };
48 |       } catch (error) {
49 |         console.error('Error message:', error);
50 |         return {
51 |           content: [
52 |             { type: "text", text: `Error: ${(error as Error).message}` }
53 |           ]
54 |         };
55 |       }
56 |     }
57 |   );
58 | };
59 | ```
60 | 
61 | For more information, see the [Widget Tools Guide](/docs/widget-tools-guide.md).
62 | 
```

--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------

```markdown
 1 | # Figma MCP Server Documentation
 2 | 
 3 | Welcome to the documentation for the Figma MCP (Model Context Protocol) Server. This documentation provides comprehensive information about the project's purpose, implementation, features, and usage.
 4 | 
 5 | ## Table of Contents
 6 | 
 7 | 1. [Project Overview](./01-overview.md)
 8 |    - Introduction and purpose
 9 |    - Core features
10 |    - Technology stack
11 |    - Project structure
12 |    - Integration with AI systems
13 | 
14 | 2. [Implementation Steps](./02-implementation-steps.md)
15 |    - Project setup
16 |    - Configuration
17 |    - Figma API integration
18 |    - MCP server implementation
19 |    - Build system
20 |    - Documentation
21 |    - Testing and verification
22 | 
23 | 3. [Components and Features](./03-components-and-features.md)
24 |    - Core components
25 |    - MCP tools
26 |    - Resource templates
27 |    - Error handling
28 |    - Response formatting
29 | 
30 | 4. [Usage Guide](./04-usage-guide.md)
31 |    - Setup instructions
32 |    - Running the server
33 |    - Using MCP tools (with examples)
34 |    - Using resource templates
35 |    - Error handling examples
36 |    - Tips and best practices
37 | 
38 | 5. [Project Status and Roadmap](./05-project-status.md)
39 |    - Current status
40 |    - Next steps
41 |    - Version history
42 |    - Known issues
43 |    - Contribution guidelines
44 |    - Support and feedback
45 | 
46 | ## Quick Start
47 | 
48 | To get started with the Figma MCP server:
49 | 
50 | 1. Install dependencies:
51 |    ```bash
52 |    make install
53 |    ```
54 | 
55 | 2. Configure your Figma API token in `.env`
56 | 
57 | 3. Start the server:
58 |    ```bash
59 |    make mcp
60 |    ```
61 | 
62 | For more detailed instructions, see the [Usage Guide](./04-usage-guide.md).
63 | 
64 | ## Project Status
65 | 
66 | The project is currently in its initial release version with core functionality implemented. See [Project Status and Roadmap](./05-project-status.md) for more details on current status and future plans.
67 | 
68 | ## Contributing
69 | 
70 | Contributions are welcome! See the [Contribution Guidelines](./05-project-status.md#5-contribution-guidelines) for more information on how to contribute to the project.
71 | 
72 | ---
73 | 
74 | Last updated: April 13, 2025
75 | 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | [![MSeeP.ai Security Assessment Badge](https://mseep.net/pr/sichang824-mcp-figma-badge.png)](https://mseep.ai/app/sichang824-mcp-figma)
  2 | 
  3 | # Figma MCP Server
  4 | 
  5 | A Figma API server implementation based on Model Context Protocol (MCP), supporting Figma plugin and widget integration.
  6 | 
  7 | ## Features
  8 | 
  9 | - Interact with Figma API through MCP
 10 | - WebSocket server for Figma plugin communication
 11 | - Support for Figma widget development
 12 | - Environment variable configuration via command line arguments
 13 | - Rich set of Figma operation tools
 14 | 
 15 | ## Installation
 16 | 
 17 | 1. Clone the repository:
 18 | 
 19 | ```bash
 20 | git clone <repository-url>
 21 | cd figma-mcp
 22 | ```
 23 | 
 24 | 2. Install dependencies:
 25 | 
 26 | ```bash
 27 | bun install
 28 | ```
 29 | 
 30 | ## Configuration
 31 | 
 32 | ### Environment Variables
 33 | 
 34 | Create a `.env` file and set the following environment variables:
 35 | 
 36 | ```
 37 | FIGMA_PERSONAL_ACCESS_TOKEN=your_figma_token
 38 | PORT=3001
 39 | NODE_ENV=development
 40 | ```
 41 | 
 42 | ### Getting a Figma Access Token
 43 | 
 44 | 1. Log in to [Figma](https://www.figma.com/)
 45 | 2. Go to Account Settings > Personal Access Tokens
 46 | 3. Create a new access token
 47 | 4. Copy the token to your `.env` file or pass it via command line arguments
 48 | 
 49 | ## Usage
 50 | 
 51 | ### Build the Project
 52 | 
 53 | ```bash
 54 | bun run build
 55 | ```
 56 | 
 57 | ### Run the Development Server
 58 | 
 59 | ```bash
 60 | bun run dev
 61 | ```
 62 | 
 63 | ### Using Command Line Arguments
 64 | 
 65 | Support for setting environment variables via the `-e` parameter:
 66 | 
 67 | ```bash
 68 | bun --watch src/index.ts -e FIGMA_PERSONAL_ACCESS_TOKEN=your_token -e PORT=6000
 69 | ```
 70 | 
 71 | You can also use a dedicated token parameter:
 72 | 
 73 | ```bash
 74 | bun --watch src/index.ts --token your_token
 75 | ```
 76 | 
 77 | Or its shorthand:
 78 | 
 79 | ```bash
 80 | bun --watch src/index.ts -t your_token
 81 | ```
 82 | 
 83 | ## Configuring MCP in Cursor
 84 | 
 85 | Add to the `.cursor/mcp.json` file:
 86 | 
 87 | ```json
 88 | {
 89 |   "Figma MCP": {
 90 |     "command": "bun",
 91 |     "args": [
 92 |       "--watch",
 93 |       "/path/to/figma-mcp/src/index.ts",
 94 |       "-e",
 95 |       "FIGMA_PERSONAL_ACCESS_TOKEN=your_token_here",
 96 |       "-e",
 97 |       "PORT=6000"
 98 |     ]
 99 |   }
100 | }
101 | ```
102 | 
103 | ## Available Tools
104 | 
105 | The server provides the following Figma operation tools:
106 | 
107 | - File operations: Get files, versions, etc.
108 | - Node operations: Get and manipulate Figma nodes
109 | - Comment operations: Manage comments in Figma files
110 | - Image operations: Export Figma elements as images
111 | - Search functionality: Search content in Figma files
112 | - Component operations: Manage Figma components
113 | - Canvas operations: Create rectangles, circles, text, etc.
114 | - Widget operations: Manage Figma widgets
115 | 
116 | ## Figma Plugin Development
117 | 
118 | ### Plugin Introduction
119 | 
120 | Figma plugins are customized tools that extend Figma's functionality, enabling workflow automation, adding new features, or integrating with external services. This MCP server provides a convenient way to develop, test, and deploy Figma plugins.
121 | 
122 | ### Building and Testing
123 | 
124 | Build the plugin:
125 | 
126 | ```bash
127 | bun run build:plugin
128 | ```
129 | 
130 | Run in development mode:
131 | 
132 | ```bash
133 | bun run dev:plugin
134 | ```
135 | 
136 | ### Loading the Plugin in Figma
137 | 
138 | ![image](./docs/image.png)
139 | 
140 | 1. Right-click in Figma to open the menu -> Plugins -> Development -> Import plugin from manifest...
141 | 2. Select the plugin's `manifest.json` file
142 | 3. Your plugin will now appear in Figma's plugin menu
143 | 
144 | ### Plugin Interaction with MCP Server
145 | 
146 | Plugins can communicate with the MCP server via WebSocket to achieve:
147 | 
148 | - Complex data processing
149 | - External API integration
150 | - Cross-session data persistence
151 | - AI functionality integration
152 | 
153 | ## Development
154 | 
155 | ### Build Widget
156 | 
157 | ```bash
158 | bun run build:widget
159 | ```
160 | 
161 | ### Build Plugin
162 | 
163 | ```bash
164 | bun run build:plugin
165 | ```
166 | 
167 | ### Development Mode
168 | 
169 | ```bash
170 | bun run dev:widget  # Widget development mode
171 | bun run dev:plugin  # Plugin development mode
172 | ```
173 | 
174 | ## License
175 | 
176 | MIT
177 | 
```

--------------------------------------------------------------------------------
/src/widget-tools.ts:
--------------------------------------------------------------------------------

```typescript
1 | 
```

--------------------------------------------------------------------------------
/src/plugin/manifest.json:
--------------------------------------------------------------------------------

```json
1 | {
2 |   "name": "Figma MCP Canvas Operation Tool",
3 |   "id": "figma-mcp-canvas-tools",
4 |   "api": "1.0.0",
5 |   "main": "code.js",
6 |   "ui": "ui.html",
7 |   "editorType": ["figma"],
8 |   "permissions": []
9 | } 
```

--------------------------------------------------------------------------------
/manifest.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "MCP Figma Widget",
 3 |   "id": "mcp-figma-widget",
 4 |   "api": "1.0.0",
 5 |   "main": "dist/widget-code.js",
 6 |   "capabilities": ["network-access"],
 7 |   "editorType": ["figma"],
 8 |   "containsWidget": true,
 9 |   "widgetApi": "1.0.0"
10 | }
11 | 
```

--------------------------------------------------------------------------------
/src/plugin/tsconfig.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "es6",
 4 |     "lib": ["es6", "dom"],
 5 |     "typeRoots": ["../../node_modules/@types", "../../node_modules/@figma"],
 6 |     "moduleResolution": "node",
 7 |     "strict": true
 8 |   },
 9 |   "include": ["*.ts", "utils/**/*.ts"],
10 |   "exclude": ["node_modules"]
11 | } 
```

--------------------------------------------------------------------------------
/tsconfig.widget.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "compilerOptions": {
 3 |     "jsx": "react",
 4 |     "jsxFactory": "figma.widget.h",
 5 |     "jsxFragmentFactory": "figma.widget.Fragment",
 6 |     "target": "es6",
 7 |     "strict": true,
 8 |     "typeRoots": [
 9 |       "./node_modules/@types",
10 |       "./node_modules/@figma"
11 |     ]
12 |   },
13 |   "include": ["src/widget/**/*"],
14 |   "exclude": ["node_modules", "dist"]
15 | }
16 | 
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "ES2020",
 4 |     "module": "NodeNext",
 5 |     "moduleResolution": "NodeNext",
 6 |     "esModuleInterop": true,
 7 |     "forceConsistentCasingInFileNames": true,
 8 |     "strict": true,
 9 |     "skipLibCheck": true,
10 |     "resolveJsonModule": true,
11 |     "outDir": "dist",
12 |     "declaration": true,
13 |     "declarationDir": "dist/types",
14 |     "baseUrl": ".",
15 |     "paths": {
16 |       "~/*": ["src/*"]
17 |     }
18 |   },
19 |   "include": ["src/**/*", "types/**/*"],
20 |   "exclude": ["node_modules", "dist"]
21 | }
22 | 
```

--------------------------------------------------------------------------------
/src/utils.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * Utility functions for Figma MCP Server
 3 |  */
 4 | 
 5 | /**
 6 |  * Log function that writes to stderr instead of stdout
 7 |  * to avoid interfering with MCP stdio communication
 8 |  */
 9 | export function log(message: string): void {
10 |   process.stderr.write(`${message}\n`);
11 | }
12 | 
13 | /**
14 |  * Error log function that writes to stderr
15 |  */
16 | export function logError(message: string, error?: unknown): void {
17 |   const errorMessage = error instanceof Error 
18 |     ? error.message 
19 |     : error ? String(error) : 'Unknown error';
20 |   
21 |   process.stderr.write(`ERROR: ${message}: ${errorMessage}\n`);
22 | } 
```

--------------------------------------------------------------------------------
/src/tools/widget/index.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * Widget Tools - Index file to export all widget-related tools
 3 |  */
 4 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
 5 | import { getWidgetsTool } from "./get-widgets.js";
 6 | import { getWidgetTool } from "./get-widget.js";
 7 | import { getWidgetSyncDataTool } from "./get-widget-sync-data.js";
 8 | import { searchWidgetsTool } from "./search-widgets.js";
 9 | import { analyzeWidgetStructureTool } from "./analyze-widget-structure.js";
10 | 
11 | /**
12 |  * Registers all widget-related tools with the MCP server
13 |  * @param server The MCP server instance
14 |  */
15 | export function registerWidgetTools(server: McpServer): void {
16 |   // Register all widget tools
17 |   getWidgetsTool(server);
18 |   getWidgetTool(server);
19 |   getWidgetSyncDataTool(server);
20 |   searchWidgetsTool(server);
21 |   analyzeWidgetStructureTool(server);
22 | }
23 | 
```

--------------------------------------------------------------------------------
/src/plugin/utils/colorUtils.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * Color utility functions for Figma plugin
 3 |  */
 4 | 
 5 | /**
 6 |  * Convert hex color to RGB object
 7 |  * @param hex Hexadecimal color string (with or without #)
 8 |  * @returns RGB object with values between 0 and 1
 9 |  */
10 | export function hexToRgb(hex: string): RGB {
11 |   hex = hex.replace('#', '');
12 |   const r = parseInt(hex.substring(0, 2), 16) / 255;
13 |   const g = parseInt(hex.substring(2, 4), 16) / 255;
14 |   const b = parseInt(hex.substring(4, 6), 16) / 255;
15 |   return { r, g, b };
16 | }
17 | 
18 | /**
19 |  * Convert RGB object to hex color string
20 |  * @param rgb RGB object with values between 0 and 1
21 |  * @returns Hexadecimal color string with #
22 |  */
23 | export function rgbToHex(rgb: RGB): string {
24 |   const r = Math.round(rgb.r * 255).toString(16).padStart(2, '0');
25 |   const g = Math.round(rgb.g * 255).toString(16).padStart(2, '0');
26 |   const b = Math.round(rgb.b * 255).toString(16).padStart(2, '0');
27 |   return `#${r}${g}${b}`;
28 | }
29 | 
30 | /**
31 |  * Create a solid color paint
32 |  * @param color RGB color object or hex string
33 |  * @returns Solid paint object
34 |  */
35 | export function createSolidPaint(color: RGB | string): SolidPaint {
36 |   if (typeof color === 'string') {
37 |     return { type: 'SOLID', color: hexToRgb(color) };
38 |   }
39 |   return { type: 'SOLID', color };
40 | } 
```

--------------------------------------------------------------------------------
/src/tools/index.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * Tools - Main index file for all MCP tools
 3 |  */
 4 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
 5 | import { registerWidgetTools } from "./widget/index.js";
 6 | import { registerFileTools } from "./file.js";
 7 | import { registerNodeTools } from "./node.js";
 8 | import { registerCommentTools } from "./comment.js";
 9 | import { registerImageTools } from "./image.js";
10 | import { registerVersionTools } from "./version.js";
11 | import { registerSearchTools } from "./search.js";
12 | import { registerComponentTools } from "./component.js";
13 | import { registerFrameTools } from "./frame.js";
14 | import { registerCanvasTools } from "./canvas.js";
15 | import { registerPageTools } from "./page.js";
16 | 
17 | /**
18 |  * Registers all tools with the MCP server
19 |  * @param server The MCP server instance
20 |  */
21 | export function registerAllTools(server: McpServer): void {
22 |   // Register all tool categories
23 |   registerFileTools(server);
24 |   registerNodeTools(server);
25 |   registerCommentTools(server);
26 |   registerImageTools(server);
27 |   registerVersionTools(server);
28 |   registerSearchTools(server);
29 |   registerComponentTools(server);
30 |   registerWidgetTools(server);
31 |   registerFrameTools(server);
32 |   registerCanvasTools(server);
33 |   registerPageTools(server);
34 | }
35 | 
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "figma-mcp-server",
 3 |   "version": "1.0.0",
 4 |   "description": "MCP server for accessing Figma API with widget support",
 5 |   "type": "module",
 6 |   "main": "dist/index.js",
 7 |   "scripts": {
 8 |     "start": "bun run dist/index.js",
 9 |     "dev": "bun --watch src/index.ts",
10 |     "mcp": "bun --watch src/index.ts",
11 |     "build": "bun build src/index.ts --outdir dist --target node",
12 |     "build:mcp": "bun build src/index.ts --outdir dist --target node",
13 |     "build:widget": "bun build src/widget/widget.tsx --outfile dist/widget-code.js --target browser",
14 |     "dev:widget": "bun build src/widget/widget.tsx --outfile dist/widget-code.js --target browser --watch",
15 |     "build:plugin": "bun build src/plugin/code.ts --outfile src/plugin/code.js --target browser",
16 |     "dev:plugin": "bun build src/plugin/code.ts --outfile src/plugin/code.js --target browser --watch",
17 |     "test": "bun test"
18 |   },
19 |   "keywords": [
20 |     "figma",
21 |     "api",
22 |     "mcp",
23 |     "server",
24 |     "widget"
25 |   ],
26 |   "author": "",
27 |   "license": "MIT",
28 |   "dependencies": {
29 |     "@create-figma-plugin/ui": "^4.0.0",
30 |     "@create-figma-plugin/utilities": "^4.0.0",
31 |     "@figma/rest-api-spec": "^0.27.0",
32 |     "@figma/widget-typings": "^1.11.0",
33 |     "@modelcontextprotocol/sdk": "^1.9.0",
34 |     "@types/ws": "^8.18.1",
35 |     "axios": "^1.6.2",
36 |     "cors": "^2.8.5",
37 |     "dotenv": "^16.3.1",
38 |     "express": "^5.1.0",
39 |     "js-yaml": "^4.1.0",
40 |     "ws": "^8.18.1",
41 |     "zod": "^3.24.2"
42 |   },
43 |   "devDependencies": {
44 |     "@figma/plugin-typings": "^1.109.0",
45 |     "@types/cors": "^2.8.17",
46 |     "@types/express": "^4.17.21",
47 |     "@types/js-yaml": "^4.0.9",
48 |     "@types/node": "^20.10.0",
49 |     "typescript": "^5.3.2"
50 |   }
51 | }
```

--------------------------------------------------------------------------------
/src/tools/node.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * Node tools for the Figma MCP server
 3 |  */
 4 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
 5 | import { z } from "zod";
 6 | import figmaApi from "../services/figma-api.js";
 7 | 
 8 | export const getNodeTool = (server: McpServer) => {
 9 |   server.tool(
10 |     "get_node",
11 |     {
12 |       file_key: z.string().min(1).describe("The Figma file key to retrieve from"),
13 |       node_id: z.string().min(1).describe("The ID of the node to retrieve")
14 |     },
15 |     async ({ file_key, node_id }) => {
16 |       try {
17 |         const fileNodes = await figmaApi.getFileNodes(file_key, [node_id]);
18 |         const nodeData = fileNodes.nodes[node_id];
19 |         
20 |         if (!nodeData) {
21 |           return {
22 |             content: [
23 |               { type: "text", text: `Node ${node_id} not found in file ${file_key}` }
24 |             ]
25 |           };
26 |         }
27 |         
28 |         return {
29 |           content: [
30 |             { type: "text", text: `# Node: ${nodeData.document.name}` },
31 |             { type: "text", text: `Type: ${nodeData.document.type}` },
32 |             { type: "text", text: `ID: ${nodeData.document.id}` },
33 |             { type: "text", text: `Children: ${nodeData.document.children?.length || 0}` },
34 |             { type: "text", text: "```json\n" + JSON.stringify(nodeData.document, null, 2) + "\n```" }
35 |           ]
36 |         };
37 |       } catch (error) {
38 |         console.error('Error fetching node:', error);
39 |         return {
40 |           content: [
41 |             { type: "text", text: `Error getting node: ${(error as Error).message}` }
42 |           ]
43 |         };
44 |       }
45 |     }
46 |   );
47 | };
48 | 
49 | /**
50 |  * Registers all node-related tools with the MCP server
51 |  */
52 | export const registerNodeTools = (server: McpServer): void => {
53 |   getNodeTool(server);
54 | };
55 | 
```

--------------------------------------------------------------------------------
/prompt.md:
--------------------------------------------------------------------------------

```markdown
 1 | # Basic Command Line Coding Assistant
 2 | 
 3 | You are a command line coding assistant. Help me write and manage code using these essential terminal commands:
 4 | 
 5 | ## Basic File Operations
 6 | 
 7 | - View files recursively: `tree -fiI ".venv|node_modules|.git|dist|<MORE_IGNORE_PATTERNS>"`
 8 | - View file contents: `cat file.py`
 9 | - Search in files: `grep "function" file.py`
10 | - Search recursively: `grep -r "pattern" directory/`
11 | - Find files by name: `find . -name "*.py"`
12 | - Write to file using cat:
13 | 
14 |   ```
15 |   cat > file.py << EOF
16 |   # Add your code here
17 |   EOF
18 |   ```
19 | 
20 | - Move/rename files: `mv oldname.py newname.py`
21 | 
22 | ## Assistant Behavior
23 | 
24 | - Directly modify files without outputting code blocks
25 | - Read/Write all of docs in the project directory ./docs
26 | - Ensure code is not redundant or duplicative
27 | - Prioritize implementation logic and ask user when facing decisions
28 | - Maintain existing code style and naming conventions when modifying files
29 | - Use concise commands to execute operations efficiently
30 | - Consider performance implications when suggesting solutions
31 | - Provide clear explanation of steps taken during complex operations
32 | - Verify commands before execution, especially for destructive operations
33 | - Suggest file organization improvements when appropriate
34 | - Always write code in English, including all code, comments, and strings
35 | - After fully understanding responsibilities, respond with "Ready to start coding now"
36 | 
37 | ## Project Preferences
38 | 
39 | - TypeScript/Node.js: Use Bun instead of npm/node
40 | 
41 |   - Initialize: `bun init`
42 |   - Install packages: `bun install <package>`
43 |   - Run scripts: `bun run <script>`
44 | 
45 | - Default Project Files:
46 |   - Create Makefile
47 |   - Create .envrc:
48 | - Project dir: /Users/ann/Workspace/MCP/figma
49 | - Project language: TypeScript
50 | 
```

--------------------------------------------------------------------------------
/src/tools/version.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * Version tools for the Figma MCP server
 3 |  */
 4 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
 5 | import { z } from "zod";
 6 | import figmaApi from "../services/figma-api.js";
 7 | 
 8 | export const getFileVersionsTool = (server: McpServer) => {
 9 |   server.tool(
10 |     "get_file_versions",
11 |     {
12 |       file_key: z.string().min(1).describe("The Figma file key")
13 |     },
14 |     async ({ file_key }) => {
15 |       try {
16 |         const versionsResponse = await figmaApi.getFileVersions(file_key);
17 |         
18 |         if (!versionsResponse.versions || versionsResponse.versions.length === 0) {
19 |           return {
20 |             content: [
21 |               { type: "text", text: `No versions found for file ${file_key}` }
22 |             ]
23 |           };
24 |         }
25 |         
26 |         const versionsList = versionsResponse.versions.map((version, index) => {
27 |           return `${index + 1}. **${version.label || 'Unnamed version'}** - ${new Date(version.created_at).toLocaleString()} by ${version.user.handle}\n   ${version.description || 'No description'}`;
28 |         }).join('\n\n');
29 |         
30 |         return {
31 |           content: [
32 |             { type: "text", text: `# File Versions for ${file_key}` },
33 |             { type: "text", text: `Found ${versionsResponse.versions.length} versions:` },
34 |             { type: "text", text: versionsList }
35 |           ]
36 |         };
37 |       } catch (error) {
38 |         console.error('Error fetching file versions:', error);
39 |         return {
40 |           content: [
41 |             { type: "text", text: `Error getting file versions: ${(error as Error).message}` }
42 |           ]
43 |         };
44 |       }
45 |     }
46 |   );
47 | };
48 | 
49 | /**
50 |  * Registers all version-related tools with the MCP server
51 |  */
52 | export const registerVersionTools = (server: McpServer): void => {
53 |   getFileVersionsTool(server);
54 | };
55 | 
```

--------------------------------------------------------------------------------
/src/tools/component.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * Component tools for the Figma MCP server
 3 |  */
 4 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
 5 | import { z } from "zod";
 6 | import figmaApi from "../services/figma-api.js";
 7 | 
 8 | export const getComponentsTool = (server: McpServer) => {
 9 |   server.tool(
10 |     "get_components",
11 |     {
12 |       file_key: z.string().min(1).describe("The Figma file key")
13 |     },
14 |     async ({ file_key }) => {
15 |       try {
16 |         const componentsResponse = await figmaApi.getFileComponents(file_key);
17 |         
18 |         if (!componentsResponse.meta?.components || componentsResponse.meta.components.length === 0) {
19 |           return {
20 |             content: [
21 |               { type: "text", text: `No components found in file ${file_key}` }
22 |             ]
23 |           };
24 |         }
25 |         
26 |         const componentsList = componentsResponse.meta.components.map(component => {
27 |           return `- **${component.name}** (Key: ${component.key})\n  Description: ${component.description || 'No description'}\n  ${component.remote ? '(Remote component)' : '(Local component)'}`;
28 |         }).join('\n\n');
29 |         
30 |         return {
31 |           content: [
32 |             { type: "text", text: `# Components in file ${file_key}` },
33 |             { type: "text", text: `Found ${componentsResponse.meta.components.length} components:` },
34 |             { type: "text", text: componentsList }
35 |           ]
36 |         };
37 |       } catch (error) {
38 |         console.error('Error fetching components:', error);
39 |         return {
40 |           content: [
41 |             { type: "text", text: `Error getting components: ${(error as Error).message}` }
42 |           ]
43 |         };
44 |       }
45 |     }
46 |   );
47 | };
48 | 
49 | /**
50 |  * Registers all component-related tools with the MCP server
51 |  */
52 | export const registerComponentTools = (server: McpServer): void => {
53 |   getComponentsTool(server);
54 | };
55 | 
```

--------------------------------------------------------------------------------
/src/tools/widget/get-widgets.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * Tool: get_widgets
 3 |  * 
 4 |  * Retrieves all widget nodes from a Figma file
 5 |  */
 6 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
 7 | import { z } from "zod";
 8 | import figmaApi from "../../services/figma-api.js";
 9 | import { FigmaUtils } from "../../utils/figma-utils.js";
10 | 
11 | export const getWidgetsTool = (server: McpServer) => {
12 |   server.tool(
13 |     "get_widgets",
14 |     {
15 |       file_key: z.string().min(1).describe("The Figma file key to retrieve widgets from")
16 |     },
17 |     async ({ file_key }) => {
18 |       try {
19 |         const file = await figmaApi.getFile(file_key);
20 |         
21 |         // Find all widget nodes in the file
22 |         const widgetNodes = FigmaUtils.getNodesByType(file, 'WIDGET');
23 |         
24 |         if (widgetNodes.length === 0) {
25 |           return {
26 |             content: [
27 |               { type: "text", text: `No widgets found in file ${file_key}` }
28 |             ]
29 |           };
30 |         }
31 |         
32 |         const widgetsList = widgetNodes.map((node, index) => {
33 |           const widgetSyncData = node.widgetSync ? 
34 |             `\n   - Widget Sync Data: Available` : 
35 |             `\n   - Widget Sync Data: None`;
36 |             
37 |           return `${index + 1}. **${node.name}** (ID: ${node.id})
38 |    - Widget ID: ${node.widgetId || 'Unknown'}${widgetSyncData}`;
39 |         }).join('\n\n');
40 |         
41 |         return {
42 |           content: [
43 |             { type: "text", text: `# Widgets in file ${file_key}` },
44 |             { type: "text", text: `Found ${widgetNodes.length} widgets:` },
45 |             { type: "text", text: widgetsList }
46 |           ]
47 |         };
48 |       } catch (error) {
49 |         console.error('Error fetching widgets:', error);
50 |         return {
51 |           content: [
52 |             { type: "text", text: `Error getting widgets: ${(error as Error).message}` }
53 |           ]
54 |         };
55 |       }
56 |     }
57 |   );
58 | };
59 | 
```

--------------------------------------------------------------------------------
/src/tools/file.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * File tools for the Figma MCP server
 3 |  */
 4 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
 5 | import { z } from "zod";
 6 | import figmaApi from "../services/figma-api.js";
 7 | 
 8 | export const getFileTool = (server: McpServer) => {
 9 |   server.tool(
10 |     "get_file",
11 |     {
12 |       file_key: z.string().min(1).describe("The Figma file key to retrieve"),
13 |       return_full_file: z.boolean().default(false).describe("Whether to return the full file contents or just a summary")
14 |     },
15 |     async ({ file_key, return_full_file }) => {
16 |       try {
17 |         const file = await figmaApi.getFile(file_key);
18 |         
19 |         if (return_full_file) {
20 |           return {
21 |             content: [
22 |               { type: "text", text: `Retrieved Figma file: ${file.name}` },
23 |               { type: "text", text: JSON.stringify(file, null, 2) }
24 |             ]
25 |           };
26 |         } else {
27 |           return {
28 |             content: [
29 |               { type: "text", text: `# Figma File: ${file.name}` },
30 |               { type: "text", text: `Last modified: ${file.lastModified}` },
31 |               { type: "text", text: `Document contains ${file.document.children?.length || 0} top-level nodes.` },
32 |               { type: "text", text: `Components: ${Object.keys(file.components).length || 0}` },
33 |               { type: "text", text: `Component sets: ${Object.keys(file.componentSets).length || 0}` },
34 |               { type: "text", text: `Styles: ${Object.keys(file.styles).length || 0}` }
35 |             ]
36 |           };
37 |         }
38 |       } catch (error) {
39 |         console.error('Error fetching file:', error);
40 |         return {
41 |           content: [
42 |             { type: "text", text: `Error getting Figma file: ${(error as Error).message}` }
43 |           ]
44 |         };
45 |       }
46 |     }
47 |   );
48 | };
49 | 
50 | /**
51 |  * Registers all file-related tools with the MCP server
52 |  */
53 | export const registerFileTools = (server: McpServer): void => {
54 |   getFileTool(server);
55 | };
56 | 
```

--------------------------------------------------------------------------------
/src/tools/image.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * Image tools for the Figma MCP server
 3 |  */
 4 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
 5 | import { z } from "zod";
 6 | import figmaApi from "../services/figma-api.js";
 7 | 
 8 | export const getImagesTool = (server: McpServer) => {
 9 |   server.tool(
10 |     "get_images",
11 |     {
12 |       file_key: z.string().min(1).describe("The Figma file key"),
13 |       node_ids: z.array(z.string()).min(1).describe("The IDs of nodes to export as images"),
14 |       format: z.enum(["jpg", "png", "svg", "pdf"]).default("png").describe("Image format to export"),
15 |       scale: z.number().min(0.01).max(4).default(1).describe("Scale factor for the image (0.01 to 4)")
16 |     },
17 |     async ({ file_key, node_ids, format, scale }) => {
18 |       try {
19 |         const imagesResponse = await figmaApi.getImages(file_key, node_ids, {
20 |           format,
21 |           scale
22 |         });
23 |         
24 |         if (imagesResponse.err) {
25 |           return {
26 |             content: [
27 |               { type: "text", text: `Error getting images: ${imagesResponse.err}` }
28 |             ]
29 |           };
30 |         }
31 |         
32 |         const imageUrls = Object.entries(imagesResponse.images)
33 |           .map(([nodeId, url]) => {
34 |             if (!url) {
35 |               return `- ${nodeId}: Error generating image`;
36 |             }
37 |             return `- ${nodeId}: [Image URL](${url})`;
38 |           })
39 |           .join('\n');
40 |         
41 |         return {
42 |           content: [
43 |             { type: "text", text: `# Images for file ${file_key}` },
44 |             { type: "text", text: `Format: ${format}, Scale: ${scale}` },
45 |             { type: "text", text: imageUrls }
46 |           ]
47 |         };
48 |       } catch (error) {
49 |         console.error('Error fetching images:', error);
50 |         return {
51 |           content: [
52 |             { type: "text", text: `Error getting images: ${(error as Error).message}` }
53 |           ]
54 |         };
55 |       }
56 |     }
57 |   );
58 | };
59 | 
60 | /**
61 |  * Registers all image-related tools with the MCP server
62 |  */
63 | export const registerImageTools = (server: McpServer): void => {
64 |   getImagesTool(server);
65 | };
66 | 
```

--------------------------------------------------------------------------------
/src/tools/widget/get-widget.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * Tool: get_widget
 3 |  * 
 4 |  * Retrieves detailed information about a specific widget node
 5 |  */
 6 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
 7 | import { z } from "zod";
 8 | import figmaApi from "../../services/figma-api.js";
 9 | 
10 | export const getWidgetTool = (server: McpServer) => {
11 |   server.tool(
12 |     "get_widget",
13 |     {
14 |       file_key: z.string().min(1).describe("The Figma file key"),
15 |       node_id: z.string().min(1).describe("The ID of the widget node")
16 |     },
17 |     async ({ file_key, node_id }) => {
18 |       try {
19 |         const fileNodes = await figmaApi.getFileNodes(file_key, [node_id]);
20 |         const nodeData = fileNodes.nodes[node_id];
21 |         
22 |         if (!nodeData || nodeData.document.type !== 'WIDGET') {
23 |           return {
24 |             content: [
25 |               { type: "text", text: `Node ${node_id} not found in file ${file_key} or is not a widget` }
26 |             ]
27 |           };
28 |         }
29 |         
30 |         const widgetNode = nodeData.document;
31 |         
32 |         // Get the sync data if available
33 |         let syncDataContent = '';
34 |         if (widgetNode.widgetSync) {
35 |           try {
36 |             const syncData = JSON.parse(widgetNode.widgetSync);
37 |             syncDataContent = `\n\n## Widget Sync Data\n\`\`\`json\n${JSON.stringify(syncData, null, 2)}\n\`\`\``;
38 |           } catch (error) {
39 |             syncDataContent = '\n\n## Widget Sync Data\nError parsing widget sync data';
40 |           }
41 |         }
42 |         
43 |         return {
44 |           content: [
45 |             { type: "text", text: `# Widget: ${widgetNode.name}` },
46 |             { type: "text", text: `ID: ${widgetNode.id}` },
47 |             { type: "text", text: `Widget ID: ${widgetNode.widgetId || 'Unknown'}` },
48 |             { type: "text", text: `Has Sync Data: ${widgetNode.widgetSync ? 'Yes' : 'No'}${syncDataContent}` }
49 |           ]
50 |         };
51 |       } catch (error) {
52 |         console.error('Error fetching widget node:', error);
53 |         return {
54 |           content: [
55 |             { type: "text", text: `Error getting widget: ${(error as Error).message}` }
56 |           ]
57 |         };
58 |       }
59 |     }
60 |   );
61 | };
62 | 
```

--------------------------------------------------------------------------------
/src/tools/search.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * Search tools for the Figma MCP server
 3 |  */
 4 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
 5 | import { z } from "zod";
 6 | import figmaApi from "../services/figma-api.js";
 7 | import { FigmaUtils } from "../utils/figma-utils.js";
 8 | 
 9 | export const searchTextTool = (server: McpServer) => {
10 |   server.tool(
11 |     "search_text",
12 |     {
13 |       file_key: z.string().min(1).describe("The Figma file key"),
14 |       search_text: z.string().min(1).describe("The text to search for in the file")
15 |     },
16 |     async ({ file_key, search_text }) => {
17 |       try {
18 |         const file = await figmaApi.getFile(file_key);
19 |         
20 |         // Find all TEXT nodes
21 |         const textNodes = FigmaUtils.getNodesByType(file, 'TEXT');
22 |         
23 |         // Filter for nodes containing the search text
24 |         const matchingNodes = textNodes.filter(node => 
25 |           node.characters && node.characters.toLowerCase().includes(search_text.toLowerCase())
26 |         );
27 |         
28 |         if (matchingNodes.length === 0) {
29 |           return {
30 |             content: [
31 |               { type: "text", text: `No text matching "${search_text}" found in file ${file_key}` }
32 |             ]
33 |           };
34 |         }
35 |         
36 |         const matchesList = matchingNodes.map(node => {
37 |           const path = FigmaUtils.getNodePath(file, node.id);
38 |           return `- **${node.name}** (ID: ${node.id})\n  Path: ${path.join(' > ')}\n  Text: "${node.characters}"`;
39 |         }).join('\n\n');
40 |         
41 |         return {
42 |           content: [
43 |             { type: "text", text: `# Text Search Results for "${search_text}"` },
44 |             { type: "text", text: `Found ${matchingNodes.length} matching text nodes:` },
45 |             { type: "text", text: matchesList }
46 |           ]
47 |         };
48 |       } catch (error) {
49 |         console.error('Error searching text:', error);
50 |         return {
51 |           content: [
52 |             { type: "text", text: `Error searching text: ${(error as Error).message}` }
53 |           ]
54 |         };
55 |       }
56 |     }
57 |   );
58 | };
59 | 
60 | /**
61 |  * Registers all search-related tools with the MCP server
62 |  */
63 | export const registerSearchTools = (server: McpServer): void => {
64 |   searchTextTool(server);
65 | };
66 | 
```

--------------------------------------------------------------------------------
/src/tools/widget/get-widget-sync-data.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * Tool: get_widget_sync_data
 3 |  * 
 4 |  * Retrieves the synchronized state data for a specific widget
 5 |  */
 6 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
 7 | import { z } from "zod";
 8 | import figmaApi from "../../services/figma-api.js";
 9 | 
10 | export const getWidgetSyncDataTool = (server: McpServer) => {
11 |   server.tool(
12 |     "get_widget_sync_data",
13 |     {
14 |       file_key: z.string().min(1).describe("The Figma file key"),
15 |       node_id: z.string().min(1).describe("The ID of the widget node")
16 |     },
17 |     async ({ file_key, node_id }) => {
18 |       try {
19 |         const fileNodes = await figmaApi.getFileNodes(file_key, [node_id]);
20 |         const nodeData = fileNodes.nodes[node_id];
21 |         
22 |         if (!nodeData || nodeData.document.type !== 'WIDGET') {
23 |           return {
24 |             content: [
25 |               { type: "text", text: `Node ${node_id} not found in file ${file_key} or is not a widget` }
26 |             ]
27 |           };
28 |         }
29 |         
30 |         const widgetNode = nodeData.document;
31 |         
32 |         if (!widgetNode.widgetSync) {
33 |           return {
34 |             content: [
35 |               { type: "text", text: `Widget ${node_id} does not have any sync data` }
36 |             ]
37 |           };
38 |         }
39 |         
40 |         try {
41 |           const syncData = JSON.parse(widgetNode.widgetSync);
42 |           
43 |           return {
44 |             content: [
45 |               { type: "text", text: `# Widget Sync Data for "${widgetNode.name}"` },
46 |               { type: "text", text: `Widget ID: ${widgetNode.id}` },
47 |               { type: "text", text: "```json\n" + JSON.stringify(syncData, null, 2) + "\n```" }
48 |             ]
49 |           };
50 |         } catch (error) {
51 |           console.error('Error parsing widget sync data:', error);
52 |           return {
53 |             content: [
54 |               { type: "text", text: `Error parsing widget sync data: ${(error as Error).message}` }
55 |             ]
56 |           };
57 |         }
58 |       } catch (error) {
59 |         console.error('Error fetching widget sync data:', error);
60 |         return {
61 |           content: [
62 |             { type: "text", text: `Error getting widget sync data: ${(error as Error).message}` }
63 |           ]
64 |         };
65 |       }
66 |     }
67 |   );
68 | };
69 | 
```

--------------------------------------------------------------------------------
/docs/01-overview.md:
--------------------------------------------------------------------------------

```markdown
 1 | # Figma MCP Server - Project Overview
 2 | 
 3 | ## Introduction
 4 | 
 5 | The Figma MCP Server is a Model Context Protocol (MCP) implementation that provides a bridge between AI assistants and the Figma API. This allows AI systems to interact with Figma files, components, and other resources through a standardized protocol, enabling rich integrations and automations with Figma designs.
 6 | 
 7 | ## Purpose
 8 | 
 9 | This project aims to:
10 | 
11 | 1. Provide AI assistants with the ability to access and manipulate Figma files
12 | 2. Enable structured access to Figma resources through the MCP protocol
13 | 3. Bridge the gap between design tools and AI systems
14 | 4. Support design workflows with AI-assisted operations
15 | 
16 | ## Core Features
17 | 
18 | - **File Access**: Retrieve Figma files and inspect their contents
19 | - **Node Operations**: Access specific nodes within Figma files
20 | - **Comment Management**: Read and write comments on Figma files
21 | - **Image Export**: Export nodes as images in various formats
22 | - **Search Capabilities**: Search for text and elements within files
23 | - **Component Access**: View and work with Figma components
24 | - **Version History**: Access file version history
25 | 
26 | ## Technology Stack
27 | 
28 | - **TypeScript**: Type-safe implementation
29 | - **Bun**: JavaScript/TypeScript runtime and package manager
30 | - **MCP SDK**: Model Context Protocol implementation
31 | - **Figma REST API**: Official Figma API with TypeScript definitions
32 | - **Zod**: Schema validation for parameters and configurations
33 | 
34 | ## Project Structure
35 | 
36 | ```
37 | /figma
38 | ├── dist/                 # Compiled output
39 | ├── docs/                 # Documentation
40 | ├── src/
41 | │   ├── config/           # Configuration files
42 | │   ├── services/         # API and external service integrations
43 | │   └── utils/            # Utility functions
44 | ├── types/                # Type definitions
45 | ├── .env                  # Environment variables
46 | ├── Makefile              # Build and run commands
47 | ├── README.md             # Project README
48 | ├── package.json          # Dependencies and scripts
49 | └── tsconfig.json         # TypeScript configuration
50 | ```
51 | 
52 | ## Integration with AI Systems
53 | 
54 | This MCP server enables AI assistants to:
55 | 
56 | 1. Retrieve design information from Figma
57 | 2. Answer questions about design files
58 | 3. Generate images and assets from Figma files
59 | 4. Add comments and feedback to designs
60 | 5. Search for specific elements or text within designs
61 | 6. Track version history and changes
62 | 
63 | With these capabilities, AI systems can provide more contextual and helpful responses when users ask about their Figma designs, streamlining the design workflow and enhancing collaboration between designers and stakeholders.
64 | 
```

--------------------------------------------------------------------------------
/src/tools/utils/widget-utils.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Widget Utils - Helper functions for widget tools
  3 |  */
  4 | import type { Node } from '@figma/rest-api-spec';
  5 | 
  6 | /**
  7 |  * Parses the widget sync data from a widget node
  8 |  * @param node A Figma node of type WIDGET
  9 |  * @returns The parsed sync data object or null if not available/invalid
 10 |  */
 11 | export function parseWidgetSyncData(node: Node): Record<string, any> | null {
 12 |   if (node.type !== 'WIDGET' || !node.widgetSync) {
 13 |     return null;
 14 |   }
 15 |   
 16 |   try {
 17 |     return JSON.parse(node.widgetSync);
 18 |   } catch (error) {
 19 |     console.error('Error parsing widget sync data:', error);
 20 |     return null;
 21 |   }
 22 | }
 23 | 
 24 | /**
 25 |  * Formats widget sync data as a string
 26 |  * @param syncData The widget sync data object
 27 |  * @returns A formatted string representation of the sync data
 28 |  */
 29 | export function formatWidgetSyncData(syncData: Record<string, any> | null): string {
 30 |   if (!syncData) {
 31 |     return 'No sync data available';
 32 |   }
 33 |   
 34 |   return JSON.stringify(syncData, null, 2);
 35 | }
 36 | 
 37 | /**
 38 |  * Gets a summary of the widget's properties
 39 |  * @param node A Figma node of type WIDGET
 40 |  * @returns A summary object with key widget properties
 41 |  */
 42 | export function getWidgetSummary(node: Node): Record<string, any> {
 43 |   if (node.type !== 'WIDGET') {
 44 |     return { error: 'Not a widget node' };
 45 |   }
 46 |   
 47 |   const summary: Record<string, any> = {
 48 |     id: node.id,
 49 |     name: node.name,
 50 |     type: 'WIDGET',
 51 |     widgetId: node.widgetId || 'Unknown',
 52 |   };
 53 |   
 54 |   // If there's widget sync data, analyze it
 55 |   if (node.widgetSync) {
 56 |     try {
 57 |       const syncData = JSON.parse(node.widgetSync);
 58 |       const syncKeys = Object.keys(syncData);
 59 |       
 60 |       summary.syncDataKeys = syncKeys;
 61 |       summary.hasSyncData = syncKeys.length > 0;
 62 |     } catch (error) {
 63 |       summary.hasSyncData = false;
 64 |       summary.syncDataError = 'Invalid sync data format';
 65 |     }
 66 |   } else {
 67 |     summary.hasSyncData = false;
 68 |   }
 69 |   
 70 |   return summary;
 71 | }
 72 | 
 73 | /**
 74 |  * Creates a human-readable description of the widget
 75 |  * @param node A Figma node of type WIDGET
 76 |  * @returns A detailed text description of the widget
 77 |  */
 78 | export function createWidgetDescription(node: Node): string {
 79 |   if (node.type !== 'WIDGET') {
 80 |     return 'Not a widget node';
 81 |   }
 82 |   
 83 |   let description = `Widget "${node.name}" (ID: ${node.id})`;
 84 |   
 85 |   if (node.widgetId) {
 86 |     description += `\nWidget ID: ${node.widgetId}`;
 87 |   }
 88 |   
 89 |   if (node.widgetSync) {
 90 |     try {
 91 |       const syncData = JSON.parse(node.widgetSync);
 92 |       const syncKeys = Object.keys(syncData);
 93 |       
 94 |       description += `\nSync Data Keys: ${syncKeys.join(', ')}`;
 95 |     } catch (error) {
 96 |       description += '\nSync Data: [Invalid format]';
 97 |     }
 98 |   } else {
 99 |     description += '\nSync Data: None';
100 |   }
101 |   
102 |   return description;
103 | }
104 | 
```

--------------------------------------------------------------------------------
/src/plugin/utils/nodeUtils.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * Utility functions for handling Figma nodes
 3 |  */
 4 | 
 5 | /**
 6 |  * Apply common properties to any node type
 7 |  * Safely handles properties that might not be available on all node types
 8 |  * 
 9 |  * @param node The target node to apply properties to
10 |  * @param data Object containing the properties to apply
11 |  */
12 | export function applyCommonProperties(node: SceneNode, data: any): void {
13 |   // Position
14 |   if (data.x !== undefined) node.x = data.x;
15 |   if (data.y !== undefined) node.y = data.y;
16 |   
17 |   // Name
18 |   if (data.name) node.name = data.name;
19 |   
20 |   // Properties that aren't available on all node types
21 |   // We need to check if they exist before setting them
22 |   
23 |   // Opacity
24 |   if (data.opacity !== undefined && 'opacity' in node) {
25 |     (node as any).opacity = data.opacity;
26 |   }
27 |   
28 |   // Blend mode
29 |   if (data.blendMode && 'blendMode' in node) {
30 |     (node as any).blendMode = data.blendMode;
31 |   }
32 |   
33 |   // Effects
34 |   if (data.effects && 'effects' in node) {
35 |     (node as any).effects = data.effects;
36 |   }
37 |   
38 |   // Constraint
39 |   if (data.constraints && 'constraints' in node) {
40 |     (node as any).constraints = data.constraints;
41 |   }
42 |   
43 |   // Is Mask
44 |   if (data.isMask !== undefined && 'isMask' in node) {
45 |     (node as any).isMask = data.isMask;
46 |   }
47 |   
48 |   // Visible
49 |   if (data.visible !== undefined) node.visible = data.visible;
50 |   
51 |   // Locked
52 |   if (data.locked !== undefined) node.locked = data.locked;
53 | }
54 | 
55 | /**
56 |  * Select and focus on a node or set of nodes
57 |  * @param nodes Node or array of nodes to focus on
58 |  */
59 | export function selectAndFocusNodes(nodes: SceneNode | SceneNode[]): void {
60 |   const nodesToFocus = Array.isArray(nodes) ? nodes : [nodes];
61 |   figma.currentPage.selection = nodesToFocus;
62 |   figma.viewport.scrollAndZoomIntoView(nodesToFocus);
63 | }
64 | 
65 | /**
66 |  * Build a result object from a node or array of nodes
67 |  * @param result Node or array of nodes to create a result object from
68 |  * @returns Object containing node information in a consistent format
69 |  */
70 | export function buildResultObject(result: SceneNode | readonly SceneNode[] | null): {[key: string]: any} {
71 |   let resultObject: {[key: string]: any} = {};
72 |   
73 |   if (!result) return resultObject;
74 |   
75 |   if (Array.isArray(result)) {
76 |     // Handle array result (like from get-selection)
77 |     resultObject.count = result.length;
78 |     if (result.length > 0) {
79 |       resultObject.items = result.map(node => ({
80 |         id: node.id,
81 |         type: node.type,
82 |         name: node.name
83 |       }));
84 |     }
85 |   } else {
86 |     // Handle single node result - we know it's a SceneNode at this point
87 |     const node = result as SceneNode;
88 |     resultObject.id = node.id;
89 |     resultObject.type = node.type;
90 |     resultObject.name = node.name;
91 |   }
92 |   
93 |   return resultObject;
94 | } 
```

--------------------------------------------------------------------------------
/src/tools/widget/analyze-widget-structure.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * Tool: analyze_widget_structure
 3 |  * 
 4 |  * Provides a detailed analysis of a widget's structure and properties
 5 |  */
 6 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
 7 | import { z } from "zod";
 8 | import figmaApi from "../../services/figma-api.js";
 9 | 
10 | export const analyzeWidgetStructureTool = (server: McpServer) => {
11 |   server.tool(
12 |     "analyze_widget_structure",
13 |     {
14 |       file_key: z.string().min(1).describe("The Figma file key"),
15 |       node_id: z.string().min(1).describe("The ID of the widget node")
16 |     },
17 |     async ({ file_key, node_id }) => {
18 |       try {
19 |         const fileNodes = await figmaApi.getFileNodes(file_key, [node_id]);
20 |         const nodeData = fileNodes.nodes[node_id];
21 |         
22 |         if (!nodeData || nodeData.document.type !== 'WIDGET') {
23 |           return {
24 |             content: [
25 |               { type: "text", text: `Node ${node_id} not found in file ${file_key} or is not a widget` }
26 |             ]
27 |           };
28 |         }
29 |         
30 |         const widgetNode = nodeData.document;
31 |         
32 |         // Create a full analysis of the widget
33 |         const widgetAnalysis = {
34 |           basic: {
35 |             id: widgetNode.id,
36 |             name: widgetNode.name,
37 |             type: widgetNode.type,
38 |             widgetId: widgetNode.widgetId || 'Unknown'
39 |           },
40 |           placement: {
41 |             x: widgetNode.x || 0,
42 |             y: widgetNode.y || 0,
43 |             width: widgetNode.width || 0,
44 |             height: widgetNode.height || 0,
45 |             rotation: widgetNode.rotation || 0
46 |           },
47 |           syncData: null as any
48 |         };
49 |         
50 |         // Parse the widget sync data if available
51 |         if (widgetNode.widgetSync) {
52 |           try {
53 |             widgetAnalysis.syncData = JSON.parse(widgetNode.widgetSync);
54 |           } catch (error) {
55 |             widgetAnalysis.syncData = { error: 'Invalid sync data format' };
56 |           }
57 |         }
58 |         
59 |         return {
60 |           content: [
61 |             { type: "text", text: `# Widget Analysis: ${widgetNode.name}` },
62 |             { type: "text", text: `## Basic Information` },
63 |             { type: "text", text: "```json\n" + JSON.stringify(widgetAnalysis.basic, null, 2) + "\n```" },
64 |             { type: "text", text: `## Placement` },
65 |             { type: "text", text: "```json\n" + JSON.stringify(widgetAnalysis.placement, null, 2) + "\n```" },
66 |             { type: "text", text: `## Sync Data` },
67 |             { type: "text", text: widgetAnalysis.syncData ? 
68 |               "```json\n" + JSON.stringify(widgetAnalysis.syncData, null, 2) + "\n```" : 
69 |               "No sync data available" 
70 |             }
71 |           ]
72 |         };
73 |       } catch (error) {
74 |         console.error('Error analyzing widget:', error);
75 |         return {
76 |           content: [
77 |             { type: "text", text: `Error analyzing widget: ${(error as Error).message}` }
78 |           ]
79 |         };
80 |       }
81 |     }
82 |   );
83 | };
84 | 
```

--------------------------------------------------------------------------------
/src/services/websocket.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * WebSocket Service - Handles communication with Figma plugin
  3 |  */
  4 | import { WebSocketServer, WebSocket as WSWebSocket } from "ws";
  5 | import { log, logError } from "../utils.js";
  6 | 
  7 | // Store active plugin connection WebSocket
  8 | let activePluginConnection: WSWebSocket | null = null;
  9 | 
 10 | // Callbacks for handling responses
 11 | const pendingCommands = new Map<string, (response: any) => void>();
 12 | 
 13 | interface PluginResponse {
 14 |   success: boolean;
 15 |   result?: any;
 16 |   error?: string;
 17 | }
 18 | 
 19 | /**
 20 |  * Create WebSocket server
 21 |  */
 22 | export function initializeWebSocketServer(port = 3001) {
 23 |   const wss = new WebSocketServer({ port });
 24 |   log(`WebSocket server started on port ${port}`);
 25 | 
 26 |   wss.on("connection", (ws: WSWebSocket) => {
 27 |     log("New WebSocket connection");
 28 | 
 29 |     ws.on("message", (message: WSWebSocket.Data) => {
 30 |       try {
 31 |         const data = JSON.parse(message.toString());
 32 |         log(`Received WebSocket message: ${JSON.stringify(data)}`);
 33 | 
 34 |         if (data.type === "figma-plugin-connected") {
 35 |           // Store active connection
 36 |           activePluginConnection = ws;
 37 |           log(`Figma plugin connected: ${data.pluginId || "unknown"}`);
 38 |         } else if (data.type === "figma-plugin-response") {
 39 |           // Handle response from plugin
 40 |           const { command, success, result, error } = data;
 41 |           const callback = pendingCommands.get(command);
 42 | 
 43 |           if (callback) {
 44 |             callback({ success, result, error });
 45 |             pendingCommands.delete(command);
 46 |           }
 47 |         }
 48 |       } catch (error) {
 49 |         logError("Error processing WebSocket message", error);
 50 |       }
 51 |     });
 52 | 
 53 |     ws.on("close", () => {
 54 |       log("WebSocket connection closed");
 55 |       if (activePluginConnection === ws) {
 56 |         activePluginConnection = null;
 57 |       }
 58 |     });
 59 | 
 60 |     ws.on("error", (error: Error) => {
 61 |       logError("WebSocket error", error);
 62 |     });
 63 |   });
 64 | 
 65 |   return wss;
 66 | }
 67 | 
 68 | /**
 69 |  * Send command to Figma plugin
 70 |  */
 71 | export async function sendCommandToPlugin(
 72 |   command: string,
 73 |   params: any
 74 | ): Promise<PluginResponse> {
 75 |   return new Promise((resolve, reject) => {
 76 |     if (!activePluginConnection) {
 77 |       reject(new Error("No active Figma plugin connection"));
 78 |       return;
 79 |     }
 80 | 
 81 |     try {
 82 |       // Store callback
 83 |       pendingCommands.set(command, resolve);
 84 | 
 85 |       // Send command
 86 |       activePluginConnection.send(
 87 |         JSON.stringify({
 88 |           type: "mcp-command",
 89 |           command,
 90 |           params,
 91 |         })
 92 |       );
 93 | 
 94 |       // Set timeout
 95 |       setTimeout(() => {
 96 |         if (pendingCommands.has(command)) {
 97 |           pendingCommands.delete(command);
 98 |           reject(new Error(`Command ${command} timed out`));
 99 |         }
100 |       }, 10000); // 10 second timeout
101 |     } catch (error) {
102 |       pendingCommands.delete(command);
103 |       reject(error);
104 |     }
105 |   });
106 | }
107 | 
108 | /**
109 |  * Check if a Figma plugin is connected
110 |  */
111 | export function isPluginConnected(): boolean {
112 |   return activePluginConnection !== null;
113 | } 
```

--------------------------------------------------------------------------------
/src/utils/widget-utils.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Utility functions for working with Figma Widgets
  3 |  */
  4 | import type { GetFileResponse, Node } from '@figma/rest-api-spec';
  5 | import type { WidgetNode, WidgetSyncData } from '../services/widget-api.js';
  6 | 
  7 | /**
  8 |  * Utility functions for working with Figma Widgets
  9 |  */
 10 | export class WidgetUtils {
 11 |   /**
 12 |    * Find all widget nodes in a file
 13 |    */
 14 |   static findAllWidgetNodes(file: GetFileResponse): Node[] {
 15 |     const widgetNodes: Node[] = [];
 16 |     
 17 |     // Helper function to recursively search for widget nodes
 18 |     const findWidgets = (node: Node) => {
 19 |       if (node.type === 'WIDGET') {
 20 |         widgetNodes.push(node);
 21 |       }
 22 |       
 23 |       if (node.children) {
 24 |         for (const child of node.children) {
 25 |           findWidgets(child);
 26 |         }
 27 |       }
 28 |     };
 29 |     
 30 |     findWidgets(file.document);
 31 |     return widgetNodes;
 32 |   }
 33 |   
 34 |   /**
 35 |    * Extract widget sync data from a widget node
 36 |    */
 37 |   static extractWidgetSyncData(node: Node): WidgetSyncData | null {
 38 |     if (node.type !== 'WIDGET' || !node.widgetSync) {
 39 |       return null;
 40 |     }
 41 |     
 42 |     try {
 43 |       return JSON.parse(node.widgetSync);
 44 |     } catch (error) {
 45 |       console.error('Error parsing widget sync data:', error);
 46 |       return null;
 47 |     }
 48 |   }
 49 |   
 50 |   /**
 51 |    * Format widget sync data for display
 52 |    */
 53 |   static formatWidgetSyncData(syncData: WidgetSyncData | null): string {
 54 |     if (!syncData) {
 55 |       return 'No sync data available';
 56 |     }
 57 |     
 58 |     return JSON.stringify(syncData, null, 2);
 59 |   }
 60 |   
 61 |   /**
 62 |    * Get a summary of a widget node
 63 |    */
 64 |   static getWidgetSummary(node: WidgetNode): Record<string, any> {
 65 |     const { id, name, widgetId } = node;
 66 |     
 67 |     const summary: Record<string, any> = {
 68 |       id,
 69 |       name,
 70 |       type: 'WIDGET',
 71 |       widgetId: widgetId || 'Unknown',
 72 |     };
 73 |     
 74 |     // If there's widget sync data, add a summary
 75 |     if (node.widgetSync) {
 76 |       try {
 77 |         const syncData = JSON.parse(node.widgetSync);
 78 |         const syncKeys = Object.keys(syncData);
 79 |         
 80 |         summary.syncDataKeys = syncKeys;
 81 |         summary.hasSyncData = syncKeys.length > 0;
 82 |       } catch (error) {
 83 |         summary.hasSyncData = false;
 84 |         summary.syncDataError = 'Invalid sync data format';
 85 |       }
 86 |     } else {
 87 |       summary.hasSyncData = false;
 88 |     }
 89 |     
 90 |     return summary;
 91 |   }
 92 |   
 93 |   /**
 94 |    * Check if a node is a widget
 95 |    */
 96 |   static isWidgetNode(node: Node): boolean {
 97 |     return node.type === 'WIDGET';
 98 |   }
 99 |   
100 |   /**
101 |    * Create a human-readable description of a widget
102 |    */
103 |   static createWidgetDescription(widget: WidgetNode): string {
104 |     let description = ;
105 |     
106 |     if (widget.widgetId) {
107 |       description += ;
108 |     }
109 |     
110 |     if (widget.widgetSync) {
111 |       try {
112 |         const syncData = JSON.parse(widget.widgetSync);
113 |         const syncKeys = Object.keys(syncData);
114 |         
115 |         description += ;
116 |       } catch (error) {
117 |         description += '\nSync Data: [Invalid format]';
118 |       }
119 |     } else {
120 |       description += '\nSync Data: None';
121 |     }
122 |     
123 |     return description;
124 |   }
125 | }
126 | 
```

--------------------------------------------------------------------------------
/src/plugin/creators/componentCreators.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Component-related creation functions for Figma plugin
  3 |  */
  4 | 
  5 | import { applyCommonProperties } from '../utils/nodeUtils';
  6 | 
  7 | /**
  8 |  * Create a component from another node
  9 |  * @param data Configuration data with sourceNode reference
 10 |  * @returns Created component node
 11 |  */
 12 | export function createComponentFromNodeData(data: any): ComponentNode | null {
 13 |   // We need a sourceNode to create a component from
 14 |   if (!data.sourceNode) {
 15 |     console.error('createComponentFromNode requires a sourceNode');
 16 |     return null;
 17 |   }
 18 |   
 19 |   try {
 20 |     // If sourceNode is a string, try to find it by ID
 21 |     let sourceNode;
 22 |     if (typeof data.sourceNode === 'string') {
 23 |       sourceNode = figma.getNodeById(data.sourceNode);
 24 |       if (!sourceNode || !('type' in sourceNode)) {
 25 |         console.error(`Node with ID ${data.sourceNode} not found or is not a valid node`);
 26 |         return null;
 27 |       }
 28 |     } else {
 29 |       sourceNode = data.sourceNode;
 30 |     }
 31 |     
 32 |     // Create the component from the source node
 33 |     const component = figma.createComponentFromNode(sourceNode as SceneNode);
 34 |     
 35 |     // Apply component-specific properties
 36 |     if (data.description) component.description = data.description;
 37 |     
 38 |     // Apply common properties
 39 |     applyCommonProperties(component, data);
 40 |     
 41 |     return component;
 42 |   } catch (error) {
 43 |     console.error('Failed to create component from node:', error);
 44 |     return null;
 45 |   }
 46 | }
 47 | 
 48 | /**
 49 |  * Create a component set (variant container)
 50 |  * @param data Configuration data for component set
 51 |  * @returns Created component set node
 52 |  */
 53 | export function createComponentSetFromData(data: any): ComponentSetNode | null {
 54 |   try {
 55 |     // Create an empty component set
 56 |     // In practice, component sets are usually created by combining variants
 57 |     // using figma.combineAsVariants, not directly created
 58 |     
 59 |     // Get the components to combine
 60 |     if (!data.components || !Array.isArray(data.components) || data.components.length === 0) {
 61 |       console.error('Component set creation requires component nodes');
 62 |       return null;
 63 |     }
 64 |     
 65 |     const componentNodes: ComponentNode[] = [];
 66 |     
 67 |     // Collect the component nodes (could be IDs or actual nodes)
 68 |     for (const component of data.components) {
 69 |       let node;
 70 |       
 71 |       if (typeof component === 'string') {
 72 |         // If it's a string, assume it's a node ID
 73 |         node = figma.getNodeById(component);
 74 |       } else {
 75 |         node = component;
 76 |       }
 77 |       
 78 |       if (node && node.type === 'COMPONENT') {
 79 |         componentNodes.push(node as ComponentNode);
 80 |       }
 81 |     }
 82 |     
 83 |     if (componentNodes.length === 0) {
 84 |       console.error('No valid component nodes provided');
 85 |       return null;
 86 |     }
 87 |     
 88 |     // Combine the components as variants
 89 |     const componentSet = figma.combineAsVariants(componentNodes, figma.currentPage);
 90 |     
 91 |     // Apply component set properties
 92 |     if (data.name) componentSet.name = data.name;
 93 |     
 94 |     // Apply common properties
 95 |     applyCommonProperties(componentSet, data);
 96 |     
 97 |     return componentSet;
 98 |   } catch (error) {
 99 |     console.error('Failed to create component set:', error);
100 |     return null;
101 |   }
102 | } 
```

--------------------------------------------------------------------------------
/src/utils/figma-utils.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import type { GetFileResponse, Node } from '@figma/rest-api-spec';
  2 | 
  3 | /**
  4 |  * Utility functions for working with Figma files and nodes
  5 |  */
  6 | export class FigmaUtils {
  7 |   /**
  8 |    * Find a node by ID in a Figma file
  9 |    */
 10 |   static findNodeById(file: GetFileResponse, nodeId: string): Node | null {
 11 |     // If node ID is the document itself
 12 |     if (nodeId === file.document.id) {
 13 |       return file.document;
 14 |     }
 15 | 
 16 |     // Helper function to recursively search for node
 17 |     const findNode = (node: Node): Node | null => {
 18 |       if (node.id === nodeId) {
 19 |         return node;
 20 |       }
 21 | 
 22 |       if (node.children) {
 23 |         for (const child of node.children) {
 24 |           const found = findNode(child);
 25 |           if (found) return found;
 26 |         }
 27 |       }
 28 | 
 29 |       return null;
 30 |     };
 31 | 
 32 |     return findNode(file.document);
 33 |   }
 34 | 
 35 |   /**
 36 |    * Get all nodes of a specific type from a Figma file
 37 |    */
 38 |   static getNodesByType(file: GetFileResponse, type: string): Node[] {
 39 |     const nodes: Node[] = [];
 40 | 
 41 |     // Helper function to recursively search for nodes
 42 |     const findNodes = (node: Node) => {
 43 |       if (node.type === type) {
 44 |         nodes.push(node);
 45 |       }
 46 | 
 47 |       if (node.children) {
 48 |         for (const child of node.children) {
 49 |           findNodes(child);
 50 |         }
 51 |       }
 52 |     };
 53 | 
 54 |     findNodes(file.document);
 55 |     return nodes;
 56 |   }
 57 | 
 58 |   /**
 59 |    * Format a node ID for display (e.g., "1:2" -> "Node 1:2")
 60 |    */
 61 |   static formatNodeId(nodeId: string): string {
 62 |     return `Node ${nodeId}`;
 63 |   }
 64 | 
 65 |   /**
 66 |    * Extract text content from a TEXT node
 67 |    */
 68 |   static extractTextFromNode(node: Node): string {
 69 |     return node.type === 'TEXT' ? node.characters || '' : '';
 70 |   }
 71 | 
 72 |   /**
 73 |    * Get a simple representation of a node's properties
 74 |    */
 75 |   static getNodeProperties(node: Node): Record<string, any> {
 76 |     const { id, name, type } = node;
 77 |     let properties: Record<string, any> = { id, name, type };
 78 | 
 79 |     // Add type-specific properties
 80 |     switch (node.type) {
 81 |       case 'TEXT':
 82 |         properties.text = node.characters;
 83 |         break;
 84 |       case 'RECTANGLE':
 85 |       case 'ELLIPSE':
 86 |       case 'POLYGON':
 87 |       case 'STAR':
 88 |       case 'VECTOR':
 89 |         if (node.fills) {
 90 |           properties.fills = node.fills.map(fill => ({
 91 |             type: fill.type,
 92 |             visible: fill.visible,
 93 |           }));
 94 |         }
 95 |         break;
 96 |       case 'FRAME':
 97 |       case 'GROUP':
 98 |       case 'INSTANCE':
 99 |       case 'COMPONENT':
100 |         properties.childCount = node.children?.length || 0;
101 |         break;
102 |     }
103 | 
104 |     return properties;
105 |   }
106 | 
107 |   /**
108 |    * Get the path to a node in the document
109 |    */
110 |   static getNodePath(file: GetFileResponse, nodeId: string): string[] {
111 |     const path: string[] = [];
112 |     
113 |     const findPath = (node: Node, target: string): boolean => {
114 |       if (node.id === target) {
115 |         path.unshift(node.name);
116 |         return true;
117 |       }
118 |       
119 |       if (node.children) {
120 |         for (const child of node.children) {
121 |           if (findPath(child, target)) {
122 |             path.unshift(node.name);
123 |             return true;
124 |           }
125 |         }
126 |       }
127 |       
128 |       return false;
129 |     };
130 |     
131 |     findPath(file.document, nodeId);
132 |     return path;
133 |   }
134 | }
135 | 
```

--------------------------------------------------------------------------------
/docs/widget-tools-guide.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Figma Widget Tools Guide
  2 | 
  3 | This guide explains how to use the MCP server's Widget Tools for interacting with Figma widgets.
  4 | 
  5 | ## Overview
  6 | 
  7 | Widget Tools are a set of utilities that allow AI assistants to interact with and manipulate Figma widgets through the MCP protocol. These tools provide capabilities for discovering, analyzing, and working with widgets in Figma files.
  8 | 
  9 | ## Available Widget Tools
 10 | 
 11 | ### 1. get_widgets
 12 | 
 13 | Retrieves all widget nodes from a Figma file.
 14 | 
 15 | **Parameters:**
 16 | - `file_key` (string): The Figma file key to retrieve widgets from
 17 | 
 18 | **Example:**
 19 | ```json
 20 | {
 21 |   "file_key": "abcxyz123456"
 22 | }
 23 | ```
 24 | 
 25 | **Response:**
 26 | Returns a list of all widgets in the file, including their names, IDs, and whether they have sync data.
 27 | 
 28 | ### 2. get_widget
 29 | 
 30 | Retrieves detailed information about a specific widget node.
 31 | 
 32 | **Parameters:**
 33 | - `file_key` (string): The Figma file key
 34 | - `node_id` (string): The ID of the widget node
 35 | 
 36 | **Example:**
 37 | ```json
 38 | {
 39 |   "file_key": "abcxyz123456",
 40 |   "node_id": "1:123"
 41 | }
 42 | ```
 43 | 
 44 | **Response:**
 45 | Returns detailed information about the specified widget, including its sync data if available.
 46 | 
 47 | ### 3. get_widget_sync_data
 48 | 
 49 | Retrieves the synchronized state data for a specific widget.
 50 | 
 51 | **Parameters:**
 52 | - `file_key` (string): The Figma file key
 53 | - `node_id` (string): The ID of the widget node
 54 | 
 55 | **Example:**
 56 | ```json
 57 | {
 58 |   "file_key": "abcxyz123456",
 59 |   "node_id": "1:123"
 60 | }
 61 | ```
 62 | 
 63 | **Response:**
 64 | Returns the raw sync data (state) for the specified widget in JSON format.
 65 | 
 66 | ### 4. search_widgets
 67 | 
 68 | Searches for widgets that have specific sync data properties and values.
 69 | 
 70 | **Parameters:**
 71 | - `file_key` (string): The Figma file key
 72 | - `property_key` (string): The sync data property key to search for
 73 | - `property_value` (string, optional): Optional property value to match
 74 | 
 75 | **Example:**
 76 | ```json
 77 | {
 78 |   "file_key": "abcxyz123456",
 79 |   "property_key": "count",
 80 |   "property_value": "5"
 81 | }
 82 | ```
 83 | 
 84 | **Response:**
 85 | Returns a list of widgets that match the search criteria.
 86 | 
 87 | ### 5. analyze_widget_structure
 88 | 
 89 | Provides a detailed analysis of a widget's structure and properties.
 90 | 
 91 | **Parameters:**
 92 | - `file_key` (string): The Figma file key
 93 | - `node_id` (string): The ID of the widget node
 94 | 
 95 | **Example:**
 96 | ```json
 97 | {
 98 |   "file_key": "abcxyz123456",
 99 |   "node_id": "1:123"
100 | }
101 | ```
102 | 
103 | **Response:**
104 | Returns a comprehensive analysis of the widget's structure, including basic information, placement details, and sync data.
105 | 
106 | ## Widget Integration
107 | 
108 | These tools can be used to:
109 | 
110 | 1. Discover widgets in Figma files
111 | 2. Analyze widget properties and state
112 | 3. Search for widgets with specific characteristics
113 | 4. Extract widget sync data for external processing
114 | 5. Generate reports about widget usage in design files
115 | 
116 | ## Implementation Details
117 | 
118 | The widget tools use the Figma API to access widget data and analyze their properties. They are designed to work seamlessly with the MCP protocol and provide rich, structured information about widgets that can be used by AI assistants.
119 | 
120 | ## Future Enhancements
121 | 
122 | Planned enhancements for widget tools include:
123 | 
124 | - Widget state modification capabilities (requires special access)
125 | - Widget creation and deletion
126 | - Widget template libraries
127 | - Widget analytics and usage statistics
128 | 
```

--------------------------------------------------------------------------------
/src/tools/comment.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Comment tools for the Figma MCP server
  3 |  */
  4 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
  5 | import { z } from "zod";
  6 | import figmaApi from "../services/figma-api.js";
  7 | 
  8 | export const getCommentsTool = (server: McpServer) => {
  9 |   server.tool(
 10 |     "get_comments",
 11 |     {
 12 |       file_key: z.string().min(1).describe("The Figma file key to retrieve comments from")
 13 |     },
 14 |     async ({ file_key }) => {
 15 |       try {
 16 |         const commentsResponse = await figmaApi.getComments(file_key, { as_md: true });
 17 |         
 18 |         if (!commentsResponse.comments || commentsResponse.comments.length === 0) {
 19 |           return {
 20 |             content: [
 21 |               { type: "text", text: `No comments found in file ${file_key}` }
 22 |             ]
 23 |           };
 24 |         }
 25 |         
 26 |         const commentsList = commentsResponse.comments.map(comment => {
 27 |           return `- **${comment.user.handle}** (${new Date(comment.created_at).toLocaleString()}): ${comment.message}`;
 28 |         }).join('\n');
 29 |         
 30 |         return {
 31 |           content: [
 32 |             { type: "text", text: `# Comments for file ${file_key}` },
 33 |             { type: "text", text: `Found ${commentsResponse.comments.length} comments:` },
 34 |             { type: "text", text: commentsList }
 35 |           ]
 36 |         };
 37 |       } catch (error) {
 38 |         console.error('Error fetching comments:', error);
 39 |         return {
 40 |           content: [
 41 |             { type: "text", text: `Error getting comments: ${(error as Error).message}` }
 42 |           ]
 43 |         };
 44 |       }
 45 |     }
 46 |   );
 47 | };
 48 | 
 49 | export const addCommentTool = (server: McpServer) => {
 50 |   server.tool(
 51 |     "add_comment",
 52 |     {
 53 |       file_key: z.string().min(1).describe("The Figma file key"),
 54 |       message: z.string().min(1).describe("The comment text"),
 55 |       node_id: z.string().optional().describe("Optional node ID to attach the comment to")
 56 |     },
 57 |     async ({ file_key, message, node_id }) => {
 58 |       try {
 59 |         const commentData: any = { message };
 60 |         
 61 |         // If node_id is provided, create a client_meta object
 62 |         if (node_id) {
 63 |           // Create a frame offset client_meta
 64 |           commentData.client_meta = {
 65 |             node_id: node_id,
 66 |             node_offset: {
 67 |               x: 0,
 68 |               y: 0
 69 |             }
 70 |           };
 71 |         }
 72 |         
 73 |         const commentResponse = await figmaApi.postComment(file_key, commentData);
 74 |         
 75 |         return {
 76 |           content: [
 77 |             { type: "text", text: `Comment added successfully!` },
 78 |             { type: "text", text: `Comment ID: ${commentResponse.id}` },
 79 |             { type: "text", text: `By user: ${commentResponse.user.handle}` },
 80 |             { type: "text", text: `Added at: ${new Date(commentResponse.created_at).toLocaleString()}` }
 81 |           ]
 82 |         };
 83 |       } catch (error) {
 84 |         console.error('Error adding comment:', error);
 85 |         return {
 86 |           content: [
 87 |             { type: "text", text: `Error adding comment: ${(error as Error).message}` }
 88 |           ]
 89 |         };
 90 |       }
 91 |     }
 92 |   );
 93 | };
 94 | 
 95 | /**
 96 |  * Registers all comment-related tools with the MCP server
 97 |  */
 98 | export const registerCommentTools = (server: McpServer): void => {
 99 |   getCommentsTool(server);
100 |   addCommentTool(server);
101 | };
102 | 
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Figma MCP Server - Main entry point
  3 |  *
  4 |  * This server provides a Model Context Protocol (MCP) implementation
  5 |  * for interacting with the Figma API.
  6 |  */
  7 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
  8 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
  9 | import { execSync } from "child_process";
 10 | import * as dotenv from "dotenv";
 11 | import { env } from "./config/env.js";
 12 | import { registerAllResources } from "./resources.js";
 13 | import { initializeWebSocketServer } from "./services/websocket.js";
 14 | import { registerAllTools } from "./tools/index.js";
 15 | import { log } from "./utils.js";
 16 | 
 17 | // Load environment variables
 18 | dotenv.config();
 19 | 
 20 | // Check for and kill any existing processes using the same port
 21 | function killExistingProcesses() {
 22 |   try {
 23 |     const wsPort = env.WEBSOCKET_PORT || 3001;
 24 |     log(`Checking for processes using port ${wsPort}...`);
 25 | 
 26 |     // Find processes using the websocket port
 27 |     const findCmd =
 28 |       process.platform === "win32"
 29 |         ? `netstat -ano | findstr :${wsPort}`
 30 |         : `lsof -i:${wsPort} | grep LISTEN`;
 31 | 
 32 |     let output;
 33 |     try {
 34 |       output = execSync(findCmd, { encoding: "utf8" });
 35 |     } catch (e) {
 36 |       // No process found, which is fine
 37 |       log("No existing processes found.");
 38 |       return;
 39 |     }
 40 | 
 41 |     // Extract PIDs and kill them
 42 |     if (output) {
 43 |       if (process.platform === "win32") {
 44 |         // Windows: extract PID from last column
 45 |         const pids = output
 46 |           .split("\n")
 47 |           .filter((line) => line.trim())
 48 |           .map((line) => line.trim().split(/\s+/).pop())
 49 |           .filter((pid, index, self) => pid && self.indexOf(pid) === index);
 50 | 
 51 |         pids.forEach((pid) => {
 52 |           if (pid && parseInt(pid) !== process.pid) {
 53 |             try {
 54 |               execSync(`taskkill /F /PID ${pid}`);
 55 |               log(`Killed process with PID: ${pid}`);
 56 |             } catch (e) {
 57 |               log(`Failed to kill process with PID: ${pid}`);
 58 |             }
 59 |           }
 60 |         });
 61 |       } else {
 62 |         // Unix-like: extract PID from second column
 63 |         const pids = output
 64 |           .split("\n")
 65 |           .filter((line) => line.trim())
 66 |           .map((line) => {
 67 |             const parts = line.trim().split(/\s+/);
 68 |             return parts[1];
 69 |           })
 70 |           .filter((pid, index, self) => pid && self.indexOf(pid) === index);
 71 | 
 72 |         pids.forEach((pid) => {
 73 |           if (pid && parseInt(pid) !== process.pid) {
 74 |             try {
 75 |               execSync(`kill -9 ${pid}`);
 76 |               log(`Killed process with PID: ${pid}`);
 77 |             } catch (e) {
 78 |               log(`Failed to kill process with PID: ${pid}`);
 79 |             }
 80 |           }
 81 |         });
 82 |       }
 83 |     }
 84 |   } catch (error) {
 85 |     log(`Error checking for existing processes: ${error}`);
 86 |   }
 87 | }
 88 | 
 89 | // Kill any existing processes before starting
 90 | killExistingProcesses();
 91 | 
 92 | // Create an MCP server
 93 | const server = new McpServer({
 94 |   name: "Figma API",
 95 |   version: "1.0.0",
 96 | });
 97 | 
 98 | // Register all tools and resources
 99 | registerAllTools(server);
100 | registerAllResources(server);
101 | 
102 | // Initialize WebSocket server for Figma plugin communication
103 | const wsPort = env.WEBSOCKET_PORT || 3001;
104 | initializeWebSocketServer(wsPort);
105 | 
106 | // Start the MCP server with stdio transport
107 | const transport = new StdioServerTransport();
108 | server.connect(transport);
109 | 
110 | // Use logger utility to avoid interfering with stdout used by MCP
111 | log("Figma MCP Server started");
112 | 
113 | export { server };
114 | 
```

--------------------------------------------------------------------------------
/src/plugin/creators/sliceCreators.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Slice and page-related element creation functions
  3 |  */
  4 | 
  5 | import { applyCommonProperties } from "../utils/nodeUtils";
  6 | 
  7 | /**
  8 |  * Create a slice node from data
  9 |  * @param data Slice configuration data
 10 |  * @returns Created slice node
 11 |  */
 12 | export function createSliceFromData(data: any): SliceNode {
 13 |   const slice = figma.createSlice();
 14 | 
 15 |   // Set size and position
 16 |   if (data.width && data.height) {
 17 |     slice.resize(data.width, data.height);
 18 |   }
 19 | 
 20 |   if (data.x !== undefined) slice.x = data.x;
 21 |   if (data.y !== undefined) slice.y = data.y;
 22 | 
 23 |   // Apply export settings
 24 |   if (data.exportSettings && Array.isArray(data.exportSettings)) {
 25 |     slice.exportSettings = data.exportSettings;
 26 |   }
 27 | 
 28 |   // Apply common properties that apply to slices
 29 |   if (data.name) slice.name = data.name;
 30 |   if (data.visible !== undefined) slice.visible = data.visible;
 31 | 
 32 |   return slice;
 33 | }
 34 | 
 35 | /**
 36 |  * Create a page node from data
 37 |  * @param data Page configuration data
 38 |  * @returns Created page node
 39 |  */
 40 | export function createPageFromData(data: any): PageNode {
 41 |   const page = figma.createPage();
 42 | 
 43 |   // Set page name
 44 |   if (data.name) page.name = data.name;
 45 | 
 46 |   // Set background color if provided
 47 |   if (data.backgrounds) page.backgrounds = data.backgrounds;
 48 | 
 49 |   return page;
 50 | }
 51 | 
 52 | /**
 53 |  * Create a page divider (used for sections)
 54 |  * @param data Page divider configuration data
 55 |  * @returns Created page divider node
 56 |  */
 57 | export function createPageDividerFromData(data: any) {
 58 |   // Check if this method is available in the current Figma version
 59 |   if (!("createPageDivider" in figma)) {
 60 |     console.error("createPageDivider is not supported in this Figma version");
 61 |     return null;
 62 |   }
 63 | 
 64 |   try {
 65 |     // Using type assertion since API might not be recognized in all Figma versions
 66 |     const pageDivider = (figma as any).createPageDivider();
 67 | 
 68 |     // Set properties
 69 |     if (data.name) pageDivider.name = data.name;
 70 | 
 71 |     return pageDivider;
 72 |   } catch (error) {
 73 |     console.error("Failed to create page divider:", error);
 74 |     return null;
 75 |   }
 76 | }
 77 | 
 78 | /**
 79 |  * Create a slide node (for Figma Slides)
 80 |  * @param data Slide configuration data
 81 |  * @returns Created slide node
 82 |  */
 83 | export function createSlideFromData(data: any): SlideNode | null {
 84 |   // Check if this method is available in the current Figma version
 85 |   if (!("createSlide" in figma)) {
 86 |     console.error("createSlide is not supported in this Figma version");
 87 |     return null;
 88 |   }
 89 | 
 90 |   try {
 91 |     // Using type assertion since API might not be recognized
 92 |     const slide = (figma as any).createSlide();
 93 | 
 94 |     // Set slide properties
 95 |     if (data.name) slide.name = data.name;
 96 | 
 97 |     // Apply common properties
 98 |     applyCommonProperties(slide, data);
 99 | 
100 |     return slide;
101 |   } catch (error) {
102 |     console.error("Failed to create slide:", error);
103 |     return null;
104 |   }
105 | }
106 | 
107 | /**
108 |  * Create a slide row node (for Figma Slides)
109 |  * @param data Slide row configuration data
110 |  * @returns Created slide row node
111 |  */
112 | export function createSlideRowFromData(data: any): SlideRowNode | null {
113 |   // Check if this method is available in the current Figma version
114 |   if (!("createSlideRow" in figma)) {
115 |     console.error("createSlideRow is not supported in this Figma version");
116 |     return null;
117 |   }
118 | 
119 |   try {
120 |     // Using type assertion since API might not be recognized
121 |     const slideRow = (figma as any).createSlideRow();
122 | 
123 |     // Set slide row properties
124 |     if (data.name) slideRow.name = data.name;
125 | 
126 |     // Apply common properties
127 |     applyCommonProperties(slideRow, data);
128 | 
129 |     return slideRow;
130 |   } catch (error) {
131 |     console.error("Failed to create slide row:", error);
132 |     return null;
133 |   }
134 | }
135 | 
```

--------------------------------------------------------------------------------
/src/widget/utils/widget-tools.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Widget Tools - Utility functions for Figma widget development
  3 |  */
  4 | 
  5 | // Theme constants
  6 | export const COLORS = {
  7 |   primary: "#0D99FF",
  8 |   primaryHover: "#0870B8",
  9 |   secondary: "#F0F0F0",
 10 |   secondaryHover: "#E0E0E0",
 11 |   text: "#333333",
 12 |   lightText: "#666666",
 13 |   background: "#FFFFFF",
 14 |   border: "#E6E6E6",
 15 |   success: "#36B37E",
 16 |   error: "#FF5630",
 17 |   warning: "#FFAB00",
 18 | };
 19 | 
 20 | // Widget sizing helpers
 21 | export const SPACING = {
 22 |   xs: 4,
 23 |   sm: 8, 
 24 |   md: 16,
 25 |   lg: 24,
 26 |   xl: 32,
 27 | };
 28 | 
 29 | // Common shadow effects
 30 | export const EFFECTS = {
 31 |   dropShadow: {
 32 |     type: "drop-shadow" as const,
 33 |     color: { r: 0, g: 0, b: 0, a: 0.1 },
 34 |     offset: { x: 0, y: 2 },
 35 |     blur: 4,
 36 |   },
 37 |   strongShadow: {
 38 |     type: "drop-shadow" as const,
 39 |     color: { r: 0, g: 0, b: 0, a: 0.2 },
 40 |     offset: { x: 0, y: 4 },
 41 |     blur: 8,
 42 |   }
 43 | };
 44 | 
 45 | // Formatting helpers
 46 | export const formatDate = (date: Date): string => {
 47 |   return date.toLocaleDateString('en-US', {
 48 |     year: 'numeric',
 49 |     month: 'short',
 50 |     day: 'numeric'
 51 |   });
 52 | };
 53 | 
 54 | export const truncateText = (text: string, maxLength: number = 100): string => {
 55 |   if (text.length <= maxLength) return text;
 56 |   return text.substring(0, maxLength) + '...';
 57 | };
 58 | 
 59 | // Figma widget helper functions
 60 | export const createNodeId = (): string => {
 61 |   return 'id_' + Math.random().toString(36).substring(2, 11);
 62 | };
 63 | 
 64 | // UI Component generators
 65 | export type ButtonVariant = 'primary' | 'secondary' | 'danger';
 66 | 
 67 | export const buttonStyles = (variant: ButtonVariant = 'primary') => {
 68 |   switch (variant) {
 69 |     case 'primary':
 70 |       return {
 71 |         fill: COLORS.primary,
 72 |         hoverFill: COLORS.primaryHover,
 73 |         textColor: '#FFFFFF',
 74 |       };
 75 |     case 'secondary':
 76 |       return {
 77 |         fill: COLORS.secondary,
 78 |         hoverFill: COLORS.secondaryHover,
 79 |         textColor: COLORS.text,
 80 |       };
 81 |     case 'danger':
 82 |       return {
 83 |         fill: COLORS.error,
 84 |         hoverFill: '#E64C3D',
 85 |         textColor: '#FFFFFF',
 86 |       };
 87 |     default:
 88 |       return {
 89 |         fill: COLORS.primary,
 90 |         hoverFill: COLORS.primaryHover,
 91 |         textColor: '#FFFFFF',
 92 |       };
 93 |   }
 94 | };
 95 | 
 96 | // Network request utilities for widgets
 97 | export const fetchWithTimeout = async (
 98 |   url: string, 
 99 |   options: RequestInit = {}, 
100 |   timeout: number = 10000
101 | ): Promise<Response> => {
102 |   const controller = new AbortController();
103 |   const id = setTimeout(() => controller.abort(), timeout);
104 |   
105 |   try {
106 |     const response = await fetch(url, {
107 |       ...options,
108 |       signal: controller.signal
109 |     });
110 |     clearTimeout(id);
111 |     return response;
112 |   } catch (error) {
113 |     clearTimeout(id);
114 |     throw error;
115 |   }
116 | };
117 | 
118 | // Storage helpers
119 | export const saveToLocalStorage = (key: string, data: any): void => {
120 |   try {
121 |     localStorage.setItem(key, JSON.stringify(data));
122 |   } catch (error) {
123 |     console.error('Error saving to localStorage:', error);
124 |   }
125 | };
126 | 
127 | export const getFromLocalStorage = <T>(key: string, defaultValue: T): T => {
128 |   try {
129 |     const item = localStorage.getItem(key);
130 |     return item ? JSON.parse(item) : defaultValue;
131 |   } catch (error) {
132 |     console.error('Error reading from localStorage:', error);
133 |     return defaultValue;
134 |   }
135 | };
136 | 
137 | // Widget data utilities
138 | export interface WidgetData {
139 |   id: string;
140 |   name: string;
141 |   createdAt: string;
142 |   updatedAt: string;
143 |   data: Record<string, any>;
144 | }
145 | 
146 | export const createWidgetData = (name: string, data: Record<string, any> = {}): WidgetData => {
147 |   const now = new Date().toISOString();
148 |   return {
149 |     id: createNodeId(),
150 |     name,
151 |     createdAt: now,
152 |     updatedAt: now,
153 |     data
154 |   };
155 | };
156 | 
157 | export const updateWidgetData = (widgetData: WidgetData, newData: Partial<Record<string, any>>): WidgetData => {
158 |   return {
159 |     ...widgetData,
160 |     updatedAt: new Date().toISOString(),
161 |     data: { ...widgetData.data, ...newData }
162 |   };
163 | };
164 | 
```

--------------------------------------------------------------------------------
/src/tools/widget/search-widgets.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Tool: search_widgets
  3 |  * 
  4 |  * Searches for widgets that have specific sync data properties and values
  5 |  */
  6 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
  7 | import { z } from "zod";
  8 | import figmaApi from "../../services/figma-api.js";
  9 | import { FigmaUtils } from "../../utils/figma-utils.js";
 10 | 
 11 | export const searchWidgetsTool = (server: McpServer) => {
 12 |   server.tool(
 13 |     "search_widgets",
 14 |     {
 15 |       file_key: z.string().min(1).describe("The Figma file key"),
 16 |       property_key: z.string().min(1).describe("The sync data property key to search for"),
 17 |       property_value: z.string().optional().describe("Optional property value to match (if not provided, returns all widgets with the property)")
 18 |     },
 19 |     async ({ file_key, property_key, property_value }) => {
 20 |       try {
 21 |         const file = await figmaApi.getFile(file_key);
 22 |         
 23 |         // Find all widget nodes
 24 |         const allWidgetNodes = FigmaUtils.getNodesByType(file, 'WIDGET');
 25 |         
 26 |         // Filter widgets that have the specified property
 27 |         const matchingWidgets = allWidgetNodes.filter(node => {
 28 |           if (!node.widgetSync) return false;
 29 |           
 30 |           try {
 31 |             const syncData = JSON.parse(node.widgetSync);
 32 |             
 33 |             // If property_value is provided, check for exact match
 34 |             if (property_value !== undefined) {
 35 |               // Handle different types of values (string, number, boolean)
 36 |               const propValue = syncData[property_key];
 37 |               
 38 |               if (typeof propValue === 'string') {
 39 |                 return propValue === property_value;
 40 |               } else if (typeof propValue === 'number') {
 41 |                 return propValue.toString() === property_value;
 42 |               } else if (typeof propValue === 'boolean') {
 43 |                 return propValue.toString() === property_value;
 44 |               } else if (propValue !== null && typeof propValue === 'object') {
 45 |                 return JSON.stringify(propValue) === property_value;
 46 |               }
 47 |               
 48 |               return false;
 49 |             }
 50 |             
 51 |             // If no value provided, just check if the property exists
 52 |             return property_key in syncData;
 53 |           } catch (error) {
 54 |             return false;
 55 |           }
 56 |         });
 57 |         
 58 |         if (matchingWidgets.length === 0) {
 59 |           return {
 60 |             content: [
 61 |               { type: "text", text: property_value ? 
 62 |                 `No widgets found with property "${property_key}" = "${property_value}"` : 
 63 |                 `No widgets found with property "${property_key}"` 
 64 |               }
 65 |             ]
 66 |           };
 67 |         }
 68 |         
 69 |         const widgetsList = matchingWidgets.map((node, index) => {
 70 |           let syncDataValue = '';
 71 |           try {
 72 |             const syncData = JSON.parse(node.widgetSync!);
 73 |             const value = syncData[property_key];
 74 |             syncDataValue = typeof value === 'object' ? 
 75 |               JSON.stringify(value) : 
 76 |               String(value);
 77 |           } catch (error) {
 78 |             syncDataValue = 'Error parsing sync data';
 79 |           }
 80 |           
 81 |           return `${index + 1}. **${node.name}** (ID: ${node.id})
 82 |    - Property "${property_key}": ${syncDataValue}`;
 83 |         }).join('\n\n');
 84 |         
 85 |         return {
 86 |           content: [
 87 |             { type: "text", text: property_value ? 
 88 |               `# Widgets with property "${property_key}" = "${property_value}"` : 
 89 |               `# Widgets with property "${property_key}"` 
 90 |             },
 91 |             { type: "text", text: `Found ${matchingWidgets.length} matching widgets:` },
 92 |             { type: "text", text: widgetsList }
 93 |           ]
 94 |         };
 95 |       } catch (error) {
 96 |         console.error('Error searching widgets:', error);
 97 |         return {
 98 |           content: [
 99 |             { type: "text", text: `Error searching widgets: ${(error as Error).message}` }
100 |           ]
101 |         };
102 |       }
103 |     }
104 |   );
105 | };
106 | 
```

--------------------------------------------------------------------------------
/docs/02-implementation-steps.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Implementation Steps
  2 | 
  3 | This document outlines the process followed to implement the Figma MCP server, from project setup to final testing.
  4 | 
  5 | ## 1. Project Setup
  6 | 
  7 | ### Initial Directory Structure
  8 | 
  9 | The project was organized with the following structure:
 10 | - `/src` for TypeScript source files
 11 | - `/config` for configuration files
 12 | - `/services` for API integrations
 13 | - `/utils` for utility functions
 14 | - `/types` for type definitions
 15 | 
 16 | ### Dependencies Installation
 17 | 
 18 | The project uses Bun as its package manager and runtime. Key dependencies include:
 19 | - `@modelcontextprotocol/sdk` for MCP implementation
 20 | - `@figma/rest-api-spec` for Figma API type definitions
 21 | - `axios` for HTTP requests
 22 | - `zod` for schema validation
 23 | - `dotenv` for environment variable management
 24 | 
 25 | ## 2. Configuration Setup
 26 | 
 27 | ### Environment Variables
 28 | 
 29 | Created a configuration system using Zod to validate environment variables:
 30 | - `FIGMA_PERSONAL_ACCESS_TOKEN`: For Figma API authentication
 31 | - `PORT`: Server port (default: 3001)
 32 | - `NODE_ENV`: Environment (development/production)
 33 | 
 34 | ## 3. Figma API Integration
 35 | 
 36 | ### API Service Implementation
 37 | 
 38 | Created a comprehensive service for interacting with the Figma API:
 39 | - Used official Figma REST API specification types
 40 | - Implemented methods for all required API endpoints
 41 | - Added request/response handling with proper error management
 42 | - Organized methods by resource type (files, nodes, comments, etc.)
 43 | 
 44 | ### Utility Functions
 45 | 
 46 | Implemented utility functions for common operations:
 47 | - Finding nodes by ID
 48 | - Getting nodes by type
 49 | - Extracting text from nodes
 50 | - Formatting node information
 51 | - Calculating node paths in document hierarchy
 52 | 
 53 | ## 4. MCP Server Implementation
 54 | 
 55 | ### Server Setup
 56 | 
 57 | Set up the MCP server using the MCP SDK:
 58 | - Configured server metadata (name, version)
 59 | - Connected to standard I/O for communication
 60 | - Set up error handling and logging
 61 | 
 62 | ### Tools Implementation
 63 | 
 64 | Created tools for various Figma operations:
 65 | - `get_file`: Retrieve file information
 66 | - `get_node`: Access specific nodes
 67 | - `get_comments`: Read file comments
 68 | - `get_images`: Export node images
 69 | - `get_file_versions`: Access version history
 70 | - `search_text`: Search for text in files
 71 | - `get_components`: Get file components
 72 | - `add_comment`: Add comments to files
 73 | 
 74 | Each tool includes:
 75 | - Parameter validation using Zod
 76 | - Error handling and response formatting
 77 | - Proper response formatting for AI consumption
 78 | 
 79 | ### Resource Templates
 80 | 
 81 | Implemented resource templates for consistent access patterns:
 82 | - `figma-file://{file_key}`: Access to Figma files
 83 | - `figma-node://{file_key}/{node_id}`: Access to specific nodes
 84 | 
 85 | ## 5. Build System
 86 | 
 87 | ### Build Configuration
 88 | 
 89 | Set up a build system with Bun:
 90 | - Configured TypeScript compilation
 91 | - Set up build scripts for development and production
 92 | - Created a Makefile for common operations
 93 | 
 94 | ### Scripts
 95 | 
 96 | Implemented various scripts:
 97 | - `start`: Run the server
 98 | - `dev`: Development mode with auto-reload
 99 | - `mcp`: MCP server with auto-reload
100 | - `build`: Build the project
101 | - `build:mcp`: Build the MCP server
102 | - `test`: Run tests
103 | - `clean`: Clean build artifacts
104 | 
105 | ## 6. Documentation
106 | 
107 | ### README
108 | 
109 | Created a comprehensive README with:
110 | - Project description
111 | - Installation instructions
112 | - Usage examples
113 | - Available tools and resources
114 | - Development guidelines
115 | 
116 | ### Code Documentation
117 | 
118 | Added documentation throughout the codebase:
119 | - Function and method descriptions
120 | - Parameter documentation
121 | - Type definitions
122 | - Usage examples
123 | 
124 | ## 7. Testing and Verification
125 | 
126 | ### Build Verification
127 | 
128 | Verified the build process:
129 | - Confirmed successful compilation
130 | - Checked for TypeScript errors
131 | - Ensured all dependencies were properly resolved
132 | 
133 | ### File Structure Verification
134 | 
135 | Confirmed the final directory structure:
136 | - All required files in place
137 | - Proper organization of code
138 | - Correct file permissions
139 | 
140 | ## Next Steps
141 | 
142 | - **Integration Testing**: Test with real AI assistants
143 | - **Performance Optimization**: Optimize for response time
144 | - **Caching**: Add caching for frequent requests
145 | - **Extended Capabilities**: Add more tools and resources
146 | - **User Documentation**: Create end-user documentation
147 | 
```

--------------------------------------------------------------------------------
/docs/05-project-status.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Project Status and Roadmap
  2 | 
  3 | This document tracks the current status of the Figma MCP server project and outlines future development plans.
  4 | 
  5 | ## 1. Current Status
  6 | 
  7 | ### Completed Tasks
  8 | 
  9 | ✅ **Project Setup**
 10 | - Created project structure
 11 | - Installed dependencies
 12 | - Configured TypeScript environment
 13 | - Set up build system
 14 | 
 15 | ✅ **Core Components**
 16 | - Environment configuration
 17 | - Figma API service
 18 | - Utility functions
 19 | - MCP server implementation
 20 | 
 21 | ✅ **MCP Tools**
 22 | - get_file: Retrieve Figma files
 23 | - get_node: Access specific nodes
 24 | - get_comments: Read file comments
 25 | - get_images: Export node images
 26 | - get_file_versions: Access version history
 27 | - search_text: Search for text in files
 28 | - get_components: Get file components
 29 | - add_comment: Add comments to files
 30 | 
 31 | ✅ **Resource Templates**
 32 | - figma-file: Access to Figma files
 33 | - figma-node: Access to specific nodes
 34 | 
 35 | ✅ **Documentation**
 36 | - Project overview
 37 | - Implementation steps
 38 | - Components and features
 39 | - Usage guide
 40 | - Project status and roadmap
 41 | 
 42 | ### Current Limitations
 43 | 
 44 | - No authentication refresh mechanism
 45 | - Limited error reporting detail
 46 | - No caching mechanism for frequent requests
 47 | - Limited support for advanced Figma features
 48 | - No pagination support for large result sets
 49 | - Limited testing
 50 | 
 51 | ## 2. Next Steps
 52 | 
 53 | ### Short-Term Goals (Next 2-4 Weeks)
 54 | 
 55 | - [ ] **Comprehensive Testing**
 56 |   - Unit tests for all components
 57 |   - Integration tests with Figma API
 58 |   - Performance testing
 59 | 
 60 | - [ ] **Error Handling Improvements**
 61 |   - More detailed error messages
 62 |   - Better error categorization
 63 |   - Recovery mechanisms
 64 | 
 65 | - [ ] **Caching System**
 66 |   - Implement response caching
 67 |   - Configure TTL for different resource types
 68 |   - Cache invalidation mechanisms
 69 | 
 70 | - [ ] **Authentication Enhancements**
 71 |   - Token refresh mechanism
 72 |   - Better error handling for authentication issues
 73 |   - Support for OAuth authentication
 74 | 
 75 | ### Medium-Term Goals (Next 2-3 Months)
 76 | 
 77 | - [ ] **Additional Tools**
 78 |   - Team and project management
 79 |   - Style operations
 80 |   - Branch management
 81 |   - Widget interactions
 82 |   - Variable access and manipulation
 83 | 
 84 | - [ ] **Enhanced Resource Templates**
 85 |   - More granular resource access
 86 |   - Improved filtering and searching
 87 |   - Resource relationships
 88 | 
 89 | - [ ] **Performance Optimizations**
 90 |   - Parallel request processing
 91 |   - Response size optimization
 92 |   - Processing time improvements
 93 | 
 94 | - [ ] **Security Enhancements**
 95 |   - Request validation
 96 |   - Rate limiting
 97 |   - Access control for sensitive operations
 98 | 
 99 | ### Long-Term Goals (3+ Months)
100 | 
101 | - [ ] **Advanced Feature Support**
102 |   - FigJam-specific features
103 |   - Prototyping capabilities
104 |   - Dev mode integration
105 |   - Widget creation and management
106 | 
107 | - [ ] **Real-Time Updates**
108 |   - Webhook integration for file changes
109 |   - Live updates for collaborative editing
110 | 
111 | - [ ] **Extended Integration**
112 |   - Integration with other design tools
113 |   - Version control system integration
114 |   - CI/CD pipeline integration
115 | 
116 | - [ ] **Advanced AI Features**
117 |   - Design analysis capabilities
118 |   - Automated design suggestions
119 |   - Design consistency checking
120 | 
121 | ## 3. Version History
122 | 
123 | ### v1.0.0 (April 13, 2025)
124 | - Initial release
125 | - Core tools and resources
126 | - Basic documentation
127 | 
128 | ## 4. Known Issues
129 | 
130 | - Large files may cause performance issues
131 | - Certain complex node types may not be fully supported
132 | - Error handling in nested operations needs improvement
133 | - Some API rate limits may be encountered with frequent use
134 | 
135 | ## 5. Contribution Guidelines
136 | 
137 | ### Priority Areas for Contribution
138 | 
139 | 1. **Testing**: Unit and integration tests
140 | 2. **Documentation**: Usage examples and API docs
141 | 3. **Feature Expansion**: Additional tools and resources
142 | 4. **Performance**: Optimizations for large files and complex operations
143 | 5. **Error Handling**: Improved error reporting and recovery
144 | 
145 | ### Contribution Process
146 | 
147 | 1. Select an issue or feature from the project board
148 | 2. Create a branch with a descriptive name
149 | 3. Implement the change with appropriate tests
150 | 4. Submit a pull request with a clear description
151 | 5. Address review feedback
152 | 6. Merge upon approval
153 | 
154 | ## 6. Support and Feedback
155 | 
156 | For support or to provide feedback, please:
157 | - Open an issue in the GitHub repository
158 | - Contact the project maintainers
159 | - Join the project discussion forum
160 | 
161 | ---
162 | 
163 | Last updated: April 13, 2025
164 | 
```

--------------------------------------------------------------------------------
/src/services/widget-api.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Service for interacting with Figma Widget API
  3 |  */
  4 | import axios from 'axios';
  5 | import { env } from '../config/env.js';
  6 | import { z } from 'zod';
  7 | 
  8 | const FIGMA_API_BASE_URL = 'https://api.figma.com/v1';
  9 | 
 10 | // Widget data schemas
 11 | export const WidgetNodeSchema = z.object({
 12 |   id: z.string(),
 13 |   name: z.string(),
 14 |   type: z.literal('WIDGET'),
 15 |   widgetId: z.string().optional(),
 16 |   widgetSync: z.string().optional(),
 17 |   pluginData: z.record(z.unknown()).optional(),
 18 |   sharedPluginData: z.record(z.record(z.unknown())).optional(),
 19 | });
 20 | 
 21 | export type WidgetNode = z.infer<typeof WidgetNodeSchema>;
 22 | 
 23 | export const WidgetSyncDataSchema = z.record(z.unknown());
 24 | export type WidgetSyncData = z.infer<typeof WidgetSyncDataSchema>;
 25 | 
 26 | /**
 27 |  * Service for interacting with Figma Widget API
 28 |  */
 29 | export class WidgetApiService {
 30 |   private readonly headers: Record<string, string>;
 31 | 
 32 |   constructor(accessToken: string = env.FIGMA_PERSONAL_ACCESS_TOKEN) {
 33 |     this.headers = {
 34 |       'X-Figma-Token': accessToken,
 35 |     };
 36 |   }
 37 | 
 38 |   /**
 39 |    * Get all widget nodes in a file
 40 |    */
 41 |   async getWidgetNodes(fileKey: string): Promise<WidgetNode[]> {
 42 |     try {
 43 |       const response = await axios.get(, {
 44 |         headers: this.headers,
 45 |       });
 46 |       
 47 |       const file = response.data;
 48 |       return this.findAllWidgetNodes(file.document);
 49 |     } catch (error) {
 50 |       console.error('Error fetching widget nodes:', error);
 51 |       throw error;
 52 |     }
 53 |   }
 54 | 
 55 |   /**
 56 |    * Get a specific widget node by ID
 57 |    */
 58 |   async getWidgetNode(fileKey: string, nodeId: string): Promise<WidgetNode | null> {
 59 |     try {
 60 |       const response = await axios.get(, {
 61 |         headers: this.headers,
 62 |       });
 63 |       
 64 |       const node = response.data.nodes[nodeId]?.document;
 65 |       if (!node || node.type !== 'WIDGET') {
 66 |         return null;
 67 |       }
 68 |       
 69 |       return WidgetNodeSchema.parse(node);
 70 |     } catch (error) {
 71 |       console.error('Error fetching widget node:', error);
 72 |       throw error;
 73 |     }
 74 |   }
 75 | 
 76 |   /**
 77 |    * Get the widget sync data (state) for a specific widget
 78 |    */
 79 |   async getWidgetSyncData(fileKey: string, nodeId: string): Promise<WidgetSyncData | null> {
 80 |     try {
 81 |       const widgetNode = await this.getWidgetNode(fileKey, nodeId);
 82 |       
 83 |       if (!widgetNode || !widgetNode.widgetSync) {
 84 |         return null;
 85 |       }
 86 |       
 87 |       // Parse the widgetSync data string (it's stored as a JSON string)
 88 |       try {
 89 |         return JSON.parse(widgetNode.widgetSync);
 90 |       } catch (parseError) {
 91 |         console.error('Error parsing widget sync data:', parseError);
 92 |         return null;
 93 |       }
 94 |     } catch (error) {
 95 |       console.error('Error fetching widget sync data:', error);
 96 |       throw error;
 97 |     }
 98 |   }
 99 | 
100 |   /**
101 |    * Create a widget instance in a file (requires special access)
102 |    * Note: This is only available to Figma widget developers or partners.
103 |    */
104 |   async createWidget(fileKey: string, options: {
105 |     name: string,
106 |     widgetId: string,
107 |     x: number,
108 |     y: number,
109 |     initialSyncData?: Record<string, any>,
110 |     parentNodeId?: string,
111 |   }): Promise<{ widgetNodeId: string } | null> {
112 |     try {
113 |       // This endpoint might not be publicly available
114 |       const response = await axios.post(
115 |         ,
116 |         options,
117 |         { headers: this.headers }
118 |       );
119 |       
120 |       return response.data;
121 |     } catch (error) {
122 |       console.error('Error creating widget:', error);
123 |       throw error;
124 |     }
125 |   }
126 | 
127 |   /**
128 |    * Update a widget's properties (requires widget developer access)
129 |    * Note: This functionality is limited to Figma widget developers.
130 |    */
131 |   async updateWidgetProperties(fileKey: string, nodeId: string, properties: Record<string, any>): Promise<boolean> {
132 |     try {
133 |       // This endpoint might not be publicly available
134 |       await axios.patch(
135 |         ,
136 |         { properties },
137 |         { headers: this.headers }
138 |       );
139 |       
140 |       return true;
141 |     } catch (error) {
142 |       console.error('Error updating widget properties:', error);
143 |       throw error;
144 |     }
145 |   }
146 | 
147 |   /**
148 |    * Helper method to recursively find all widget nodes in a file
149 |    */
150 |   private findAllWidgetNodes(node: any): WidgetNode[] {
151 |     let widgets: WidgetNode[] = [];
152 |     
153 |     if (node.type === 'WIDGET') {
154 |       try {
155 |         widgets.push(WidgetNodeSchema.parse(node));
156 |       } catch (error) {
157 |         console.error('Error parsing widget node:', error);
158 |       }
159 |     }
160 |     
161 |     if (node.children) {
162 |       for (const child of node.children) {
163 |         widgets = widgets.concat(this.findAllWidgetNodes(child));
164 |       }
165 |     }
166 |     
167 |     return widgets;
168 |   }
169 | }
170 | 
171 | export default new WidgetApiService();
172 | 
```

--------------------------------------------------------------------------------
/src/resources.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Resources for the Figma MCP server
  3 |  */
  4 | import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
  5 | import figmaApi from "./services/figma-api.js";
  6 | 
  7 | /**
  8 |  * Resource template for Figma files
  9 |  */
 10 | export const figmaFileResource = (server: McpServer): void => {
 11 |   server.resource(
 12 |     "figma-file",
 13 |     new ResourceTemplate("figma-file://{file_key}", {
 14 |       // Define listCallback instead of just providing a string for 'list'
 15 |       listCallback: async () => {
 16 |         try {
 17 |           // Here we would typically get a list of files
 18 |           // For now, return an empty list since we don't have access to "all files"
 19 |           return {
 20 |             contents: [{
 21 |               uri: "figma-file://",
 22 |               title: "Figma Files",
 23 |               description: "List of Figma files you have access to",
 24 |               text: "# Figma Files\n\nTo access a specific file, you need to provide its file key."
 25 |             }]
 26 |           };
 27 |         } catch (error) {
 28 |           console.error('Error listing files:', error);
 29 |           return {
 30 |             contents: [{
 31 |               uri: "figma-file://",
 32 |               title: "Error listing files",
 33 |               text: `Error: ${(error as Error).message}`
 34 |             }]
 35 |           };
 36 |         }
 37 |       }
 38 |     }),
 39 |     async (uri, { file_key }) => {
 40 |       try {
 41 |         const file = await figmaApi.getFile(file_key);
 42 |         
 43 |         return {
 44 |           contents: [{
 45 |             uri: uri.href,
 46 |             title: file.name,
 47 |             description: `Last modified: ${file.lastModified}`,
 48 |             text: `# ${file.name}\n\nLast modified: ${file.lastModified}\n\nDocument contains ${file.document.children?.length || 0} top-level nodes.\nComponents: ${Object.keys(file.components).length}\nStyles: ${Object.keys(file.styles).length}`
 49 |           }]
 50 |         };
 51 |       } catch (error) {
 52 |         console.error('Error fetching file for resource:', error);
 53 |         return {
 54 |           contents: [{
 55 |             uri: uri.href,
 56 |             title: `File not found: ${file_key}`,
 57 |             text: `Error: ${(error as Error).message}`
 58 |           }]
 59 |         };
 60 |       }
 61 |     }
 62 |   );
 63 | };
 64 | 
 65 | /**
 66 |  * Resource template for Figma nodes
 67 |  */
 68 | export const figmaNodeResource = (server: McpServer): void => {
 69 |   server.resource(
 70 |     "figma-node",
 71 |     new ResourceTemplate("figma-node://{file_key}/{node_id}", {
 72 |       // Define listCallback instead of just providing a string for 'list'
 73 |       listCallback: async (uri, { file_key }) => {
 74 |         try {
 75 |           // If only file_key is provided, list all top-level nodes
 76 |           const file = await figmaApi.getFile(file_key);
 77 |           
 78 |           return {
 79 |             contents: file.document.children?.map(node => ({
 80 |               uri: `figma-node://${file_key}/${node.id}`,
 81 |               title: node.name,
 82 |               description: `Type: ${node.type}`,
 83 |               text: `# ${node.name}\n\nType: ${node.type}\nID: ${node.id}`
 84 |             })) || []
 85 |           };
 86 |         } catch (error) {
 87 |           console.error('Error listing nodes:', error);
 88 |           return {
 89 |             contents: [{
 90 |               uri: `figma-node://${file_key}`,
 91 |               title: "Error listing nodes",
 92 |               text: `Error: ${(error as Error).message}`
 93 |             }]
 94 |           };
 95 |         }
 96 |       }
 97 |     }),
 98 |     async (uri, { file_key, node_id }) => {
 99 |       try {
100 |         // Get specific node
101 |         const fileNodes = await figmaApi.getFileNodes(file_key, [node_id]);
102 |         const nodeData = fileNodes.nodes[node_id];
103 |         
104 |         if (!nodeData) {
105 |           return {
106 |             contents: [{
107 |               uri: uri.href,
108 |               title: `Node not found: ${node_id}`,
109 |               text: `Node ${node_id} not found in file ${file_key}`
110 |             }]
111 |           };
112 |         }
113 |         
114 |         return {
115 |           contents: [{
116 |             uri: uri.href,
117 |             title: nodeData.document.name,
118 |             description: `Type: ${nodeData.document.type}`,
119 |             text: `# ${nodeData.document.name}\n\nType: ${nodeData.document.type}\nID: ${nodeData.document.id}\nChildren: ${nodeData.document.children?.length || 0}`
120 |           }]
121 |         };
122 |       } catch (error) {
123 |         console.error('Error fetching node for resource:', error);
124 |         return {
125 |           contents: [{
126 |             uri: uri.href,
127 |             title: `Error`,
128 |             text: `Error: ${(error as Error).message}`
129 |           }]
130 |         };
131 |       }
132 |     }
133 |   );
134 | };
135 | 
136 | /**
137 |  * Registers all resources with the MCP server
138 |  */
139 | export function registerAllResources(server: McpServer): void {
140 |   figmaFileResource(server);
141 |   figmaNodeResource(server);
142 | }
143 | 
```

--------------------------------------------------------------------------------
/src/plugin/creators/imageCreators.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Image and media element creation functions for Figma plugin
  3 |  */
  4 | 
  5 | import { applyCommonProperties } from '../utils/nodeUtils';
  6 | 
  7 | /**
  8 |  * Create an image node from data
  9 |  * @param data Image configuration data
 10 |  * @returns Created image node
 11 |  */
 12 | export function createImageFromData(data: any): SceneNode | null {
 13 |   try {
 14 |     // Image creation requires a hash
 15 |     if (!data.hash) {
 16 |       console.error('Image creation requires an image hash');
 17 |       return null;
 18 |     }
 19 |     
 20 |     const image = figma.createImage(data.hash);
 21 |     
 22 |     // Create a rectangle to display the image
 23 |     const rect = figma.createRectangle();
 24 |     
 25 |     // Set size
 26 |     if (data.width && data.height) {
 27 |       rect.resize(data.width, data.height);
 28 |     }
 29 |     
 30 |     // Apply image as fill
 31 |     rect.fills = [{
 32 |       type: 'IMAGE',
 33 |       scaleMode: data.scaleMode || 'FILL',
 34 |       imageHash: image.hash
 35 |     }];
 36 |     
 37 |     // Apply common properties
 38 |     applyCommonProperties(rect, data);
 39 |     
 40 |     return rect;
 41 |   } catch (error) {
 42 |     console.error('Failed to create image:', error);
 43 |     return null;
 44 |   }
 45 | }
 46 | 
 47 | /**
 48 |  * Create an image node asynchronously from data
 49 |  * @param data Image configuration data with bytes or file
 50 |  * @returns Promise resolving to created image node
 51 |  */
 52 | export async function createImageFromBytesAsync(data: any): Promise<SceneNode | null> {
 53 |   try {
 54 |     // Image creation requires bytes or a file
 55 |     if (!data.bytes && !data.file) {
 56 |       console.error('Image creation requires image bytes or file');
 57 |       return null;
 58 |     }
 59 |     
 60 |     let image;
 61 |     if (data.bytes) {
 62 |       image = await figma.createImageAsync(data.bytes);
 63 |     } else if (data.file) {
 64 |       // Note: file would need to be provided through some UI interaction
 65 |       // as plugins cannot directly access the file system
 66 |       image = await figma.createImageAsync(data.file);
 67 |     } else {
 68 |       return null;
 69 |     }
 70 |     
 71 |     // Create a rectangle to display the image
 72 |     const rect = figma.createRectangle();
 73 |     
 74 |     // Set size
 75 |     if (data.width && data.height) {
 76 |       rect.resize(data.width, data.height);
 77 |     }
 78 |     
 79 |     // Apply image as fill
 80 |     rect.fills = [{
 81 |       type: 'IMAGE',
 82 |       scaleMode: data.scaleMode || 'FILL',
 83 |       imageHash: image.hash
 84 |     }];
 85 |     
 86 |     // Apply common properties
 87 |     applyCommonProperties(rect, data);
 88 |     
 89 |     return rect;
 90 |   } catch (error) {
 91 |     console.error('Failed to create image asynchronously:', error);
 92 |     return null;
 93 |   }
 94 | }
 95 | 
 96 | /**
 97 |  * Create a GIF node from data
 98 |  * @param data GIF configuration data
 99 |  * @returns Created gif node
100 |  */
101 | export function createGifFromData(data: any): SceneNode | null {
102 |   // As of my knowledge, there isn't a direct createGif API
103 |   // Even though it's in the list of methods
104 |   // For now, return null and log an error
105 |   console.error('createGif API is not directly available or implemented');
106 |   return null;
107 | }
108 | 
109 | /**
110 |  * Create a video node asynchronously from data
111 |  * This depends on figma.createVideoAsync which may not be available in all versions
112 |  * 
113 |  * @param data Video configuration data
114 |  * @returns Promise resolving to created video node
115 |  */
116 | export async function createVideoFromDataAsync(data: any): Promise<SceneNode | null> {
117 |   // Check if video creation is supported
118 |   if (!('createVideoAsync' in figma)) {
119 |     console.error('Video creation is not supported in this Figma version');
120 |     return null;
121 |   }
122 |   
123 |   try {
124 |     // Video creation requires bytes
125 |     if (!data.bytes) {
126 |       console.error('Video creation requires video bytes');
127 |       return null;
128 |     }
129 |     
130 |     // Using type assertion since createVideoAsync may not be recognized by TypeScript
131 |     const video = await (figma as any).createVideoAsync(data.bytes);
132 |     
133 |     // Apply common properties
134 |     applyCommonProperties(video, data);
135 |     
136 |     return video;
137 |   } catch (error) {
138 |     console.error('Failed to create video:', error);
139 |     return null;
140 |   }
141 | }
142 | 
143 | /**
144 |  * Create a link preview node asynchronously from data
145 |  * This depends on figma.createLinkPreviewAsync which may not be available in all versions
146 |  * 
147 |  * @param data Link preview configuration data
148 |  * @returns Promise resolving to created link preview node
149 |  */
150 | export async function createLinkPreviewFromDataAsync(data: any): Promise<SceneNode | null> {
151 |   // Check if link preview creation is supported
152 |   if (!('createLinkPreviewAsync' in figma)) {
153 |     console.error('Link preview creation is not supported in this Figma version');
154 |     return null;
155 |   }
156 |   
157 |   try {
158 |     // Link preview creation requires a URL
159 |     if (!data.url) {
160 |       console.error('Link preview creation requires a URL');
161 |       return null;
162 |     }
163 |     
164 |     // Using type assertion since createLinkPreviewAsync may not be recognized by TypeScript
165 |     const linkPreview = await (figma as any).createLinkPreviewAsync(data.url);
166 |     
167 |     // Apply common properties
168 |     applyCommonProperties(linkPreview, data);
169 |     
170 |     return linkPreview;
171 |   } catch (error) {
172 |     console.error('Failed to create link preview:', error);
173 |     return null;
174 |   }
175 | } 
```

--------------------------------------------------------------------------------
/docs/03-components-and-features.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Components and Features
  2 | 
  3 | This document provides detailed information about the key components and features of the Figma MCP server.
  4 | 
  5 | ## 1. Core Components
  6 | 
  7 | ### Environment Configuration (`src/config/env.ts`)
  8 | 
  9 | The environment configuration component:
 10 | 
 11 | - Loads variables from `.env` file using dotenv
 12 | - Validates environment variables using Zod schema
 13 | - Provides type-safe access to configuration values
 14 | - Ensures required variables are present
 15 | - Sets sensible defaults for optional variables
 16 | 
 17 | ```typescript
 18 | // Example of environment validation
 19 | const envSchema = z.object({
 20 |   FIGMA_PERSONAL_ACCESS_TOKEN: z.string().min(1),
 21 |   PORT: z.string().default("3001").transform(Number),
 22 |   NODE_ENV: z
 23 |     .enum(["development", "production", "test"])
 24 |     .default("development"),
 25 | });
 26 | ```
 27 | 
 28 | ### Figma API Service (`src/services/figma-api.ts`)
 29 | 
 30 | A comprehensive service for interacting with the Figma API:
 31 | 
 32 | - Uses official Figma API TypeScript definitions
 33 | - Provides methods for all relevant API endpoints
 34 | - Handles authentication and request formatting
 35 | - Processes responses and errors consistently
 36 | - Supports all Figma resource types:
 37 |   - Files and nodes
 38 |   - Comments
 39 |   - Images
 40 |   - Components and styles
 41 |   - Versions
 42 |   - Teams and projects
 43 | 
 44 | ```typescript
 45 | // Example method for retrieving a Figma file
 46 | async getFile(fileKey: string, params: {
 47 |   ids?: string;
 48 |   depth?: number;
 49 |   geometry?: string
 50 | } = {}): Promise<GetFileResponse> {
 51 |   const response = await axios.get(`${FIGMA_API_BASE_URL}/files/${fileKey}`, {
 52 |     headers: this.headers,
 53 |     params,
 54 |   });
 55 |   return response.data;
 56 | }
 57 | ```
 58 | 
 59 | ### Figma Utilities (`src/utils/figma-utils.ts`)
 60 | 
 61 | Utility functions for working with Figma data:
 62 | 
 63 | - Node search and traversal
 64 | - Text extraction
 65 | - Property formatting
 66 | - Path calculation
 67 | - Type-specific operations
 68 | 
 69 | ```typescript
 70 | // Example utility for finding a node by ID
 71 | static findNodeById(file: GetFileResponse, nodeId: string): Node | null {
 72 |   // Implementation details...
 73 | }
 74 | ```
 75 | 
 76 | ### MCP Server Implementation (`src/index.ts`)
 77 | 
 78 | The main MCP server implementation:
 79 | 
 80 | - Configures the MCP server
 81 | - Defines tools and resources
 82 | - Handles communication via standard I/O
 83 | - Manages error handling and response formatting
 84 | 
 85 | ```typescript
 86 | // Example of MCP server configuration
 87 | const server = new McpServer({
 88 |   name: "Figma API",
 89 |   version: "1.0.0",
 90 | });
 91 | ```
 92 | 
 93 | ## 2. MCP Tools
 94 | 
 95 | The server provides the following tools:
 96 | 
 97 | ### `get_file`
 98 | 
 99 | Retrieves a Figma file by key:
100 | 
101 | - Parameters:
102 |   - `file_key`: The Figma file key
103 |   - `return_full_file`: Whether to return the full file structure
104 | - Returns:
105 |   - File name, modification date
106 |   - Document structure summary
107 |   - Component and style counts
108 |   - Full file contents (if requested)
109 | 
110 | ### `get_node`
111 | 
112 | Retrieves a specific node from a Figma file:
113 | 
114 | - Parameters:
115 |   - `file_key`: The Figma file key
116 |   - `node_id`: The ID of the node to retrieve
117 | - Returns:
118 |   - Node name, type, and ID
119 |   - Node properties and attributes
120 |   - Child node count
121 | 
122 | ### `get_comments`
123 | 
124 | Retrieves comments from a Figma file:
125 | 
126 | - Parameters:
127 |   - `file_key`: The Figma file key
128 | - Returns:
129 |   - Comment count
130 |   - Comment text
131 |   - Author information
132 |   - Timestamps
133 | 
134 | ### `get_images`
135 | 
136 | Exports nodes as images:
137 | 
138 | - Parameters:
139 |   - `file_key`: The Figma file key
140 |   - `node_ids`: Array of node IDs to export
141 |   - `format`: Image format (jpg, png, svg, pdf)
142 |   - `scale`: Scale factor for the image
143 | - Returns:
144 |   - Image URLs for each node
145 |   - Error information for failed exports
146 | 
147 | ### `get_file_versions`
148 | 
149 | Retrieves version history for a file:
150 | 
151 | - Parameters:
152 |   - `file_key`: The Figma file key
153 | - Returns:
154 |   - Version list
155 |   - Version labels and descriptions
156 |   - Author information
157 |   - Timestamps
158 | 
159 | ### `search_text`
160 | 
161 | Searches for text within a Figma file:
162 | 
163 | - Parameters:
164 |   - `file_key`: The Figma file key
165 |   - `search_text`: The text to search for
166 | - Returns:
167 |   - Matching text nodes
168 |   - Node paths in document hierarchy
169 |   - Matching text content
170 | 
171 | ### `get_components`
172 | 
173 | Retrieves components from a Figma file:
174 | 
175 | - Parameters:
176 |   - `file_key`: The Figma file key
177 | - Returns:
178 |   - Component list
179 |   - Component names and keys
180 |   - Component descriptions
181 |   - Remote status
182 | 
183 | ### `add_comment`
184 | 
185 | Adds a comment to a Figma file:
186 | 
187 | - Parameters:
188 |   - `file_key`: The Figma file key
189 |   - `message`: The comment text
190 |   - `node_id`: Optional node ID to attach the comment to
191 | - Returns:
192 |   - Comment ID
193 |   - Author information
194 |   - Timestamp
195 | 
196 | ## 3. Resource Templates
197 | 
198 | The server provides the following resource templates:
199 | 
200 | ### `figma-file://{file_key}`
201 | 
202 | Provides access to Figma files:
203 | 
204 | - URI format: `figma-file://{file_key}`
205 | - List URI: `figma-file://`
206 | - Returns:
207 |   - File name
208 |   - Last modified date
209 |   - Document structure summary
210 | 
211 | ### `figma-node://{file_key}/{node_id}`
212 | 
213 | Provides access to nodes within Figma files:
214 | 
215 | - URI format: `figma-node://{file_key}/{node_id}`
216 | - List URI: `figma-node://{file_key}`
217 | - Returns:
218 |   - Node name and type
219 |   - Node properties
220 |   - Child node count
221 | 
222 | ## 4. Error Handling
223 | 
224 | The server implements comprehensive error handling:
225 | 
226 | - API request errors
227 | - Authentication failures
228 | - Invalid parameters
229 | - Resource not found errors
230 | - Server errors
231 | 
232 | Each error is properly formatted and returned to the client with:
233 | 
234 | - Error message
235 | - Error type
236 | - Context information (when available)
237 | 
238 | ## 5. Response Formatting
239 | 
240 | Responses are formatted for optimal consumption by AI assistants:
241 | 
242 | - Clear headings
243 | - Structured information
244 | - Formatted lists
245 | - Contextual descriptions
246 | - Links and references where appropriate
247 | 
```

--------------------------------------------------------------------------------
/docs/04-usage-guide.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Usage Guide
  2 | 
  3 | This document provides detailed instructions for setting up, running, and using the Figma MCP server.
  4 | 
  5 | ## 1. Setup Instructions
  6 | 
  7 | ### Prerequisites
  8 | 
  9 | Before you begin, ensure you have the following:
 10 | - [Bun](https://bun.sh/) v1.0.0 or higher installed
 11 | - A Figma account with API access
 12 | - A personal access token from Figma
 13 | 
 14 | ### Installation
 15 | 
 16 | 1. Clone the repository:
 17 |    ```bash
 18 |    git clone <repository-url>
 19 |    cd figma-mcp-server
 20 |    ```
 21 | 
 22 | 2. Install dependencies:
 23 |    ```bash
 24 |    make install
 25 |    ```
 26 |    or
 27 |    ```bash
 28 |    bun install
 29 |    ```
 30 | 
 31 | 3. Configure environment variables:
 32 |    - Copy the example environment file:
 33 |      ```bash
 34 |      cp .env.example .env
 35 |      ```
 36 |    - Edit `.env` and add your Figma personal access token:
 37 |      ```
 38 |      FIGMA_PERSONAL_ACCESS_TOKEN=your_figma_token_here
 39 |      PORT=3001
 40 |      NODE_ENV=development
 41 |      ```
 42 | 
 43 | ## 2. Running the Server
 44 | 
 45 | ### Development Mode
 46 | 
 47 | Run the server in development mode with auto-reload:
 48 | ```bash
 49 | make mcp
 50 | ```
 51 | or
 52 | ```bash
 53 | bun run mcp
 54 | ```
 55 | 
 56 | ### Production Mode
 57 | 
 58 | 1. Build the server:
 59 |    ```bash
 60 |    make build-mcp
 61 |    ```
 62 |    or
 63 |    ```bash
 64 |    bun run build:mcp
 65 |    ```
 66 | 
 67 | 2. Run the built server:
 68 |    ```bash
 69 |    make start
 70 |    ```
 71 |    or
 72 |    ```bash
 73 |    bun run start
 74 |    ```
 75 | 
 76 | ## 3. Using the MCP Tools
 77 | 
 78 | To use the MCP tools, you'll need an MCP client that can communicate with the server. This could be an AI assistant or another application that implements the MCP protocol.
 79 | 
 80 | ### Example: Retrieving a Figma File
 81 | 
 82 | Using the `get_file` tool:
 83 | 
 84 | ```json
 85 | {
 86 |   "tool": "get_file",
 87 |   "parameters": {
 88 |     "file_key": "abc123xyz789",
 89 |     "return_full_file": false
 90 |   }
 91 | }
 92 | ```
 93 | 
 94 | Expected response:
 95 | ```json
 96 | {
 97 |   "content": [
 98 |     { "type": "text", "text": "# Figma File: My Design" },
 99 |     { "type": "text", "text": "Last modified: 2025-04-10T15:30:45Z" },
100 |     { "type": "text", "text": "Document contains 5 top-level nodes." },
101 |     { "type": "text", "text": "Components: 12" },
102 |     { "type": "text", "text": "Component sets: 3" },
103 |     { "type": "text", "text": "Styles: 8" }
104 |   ]
105 | }
106 | ```
107 | 
108 | ### Example: Searching for Text
109 | 
110 | Using the `search_text` tool:
111 | 
112 | ```json
113 | {
114 |   "tool": "search_text",
115 |   "parameters": {
116 |     "file_key": "abc123xyz789",
117 |     "search_text": "Welcome"
118 |   }
119 | }
120 | ```
121 | 
122 | Expected response:
123 | ```json
124 | {
125 |   "content": [
126 |     { "type": "text", "text": "# Text Search Results for \"Welcome\"" },
127 |     { "type": "text", "text": "Found 2 matching text nodes:" },
128 |     { "type": "text", "text": "- **Header Text** (ID: 123:456)\n  Path: Page 1 > Header > Text\n  Text: \"Welcome to our application\"" }
129 |   ]
130 | }
131 | ```
132 | 
133 | ### Example: Adding a Comment
134 | 
135 | Using the `add_comment` tool:
136 | 
137 | ```json
138 | {
139 |   "tool": "add_comment",
140 |   "parameters": {
141 |     "file_key": "abc123xyz789",
142 |     "message": "This design looks great! Consider adjusting the contrast on the buttons.",
143 |     "node_id": "123:456"
144 |   }
145 | }
146 | ```
147 | 
148 | Expected response:
149 | ```json
150 | {
151 |   "content": [
152 |     { "type": "text", "text": "Comment added successfully!" },
153 |     { "type": "text", "text": "Comment ID: 987654" },
154 |     { "type": "text", "text": "By user: John Doe" },
155 |     { "type": "text", "text": "Added at: 4/13/2025, 12:34:56 PM" }
156 |   ]
157 | }
158 | ```
159 | 
160 | ## 4. Using Resource Templates
161 | 
162 | Resource templates provide a consistent way to access Figma resources.
163 | 
164 | ### Example: Accessing a File
165 | 
166 | Resource URI: `figma-file://abc123xyz789`
167 | 
168 | Expected response:
169 | ```json
170 | {
171 |   "contents": [{
172 |     "uri": "figma-file://abc123xyz789",
173 |     "title": "My Design",
174 |     "description": "Last modified: 2025-04-10T15:30:45Z",
175 |     "text": "# My Design\n\nLast modified: 2025-04-10T15:30:45Z\n\nDocument contains 5 top-level nodes.\nComponents: 12\nStyles: 8"
176 |   }]
177 | }
178 | ```
179 | 
180 | ### Example: Listing Nodes in a File
181 | 
182 | Resource URI: `figma-node://abc123xyz789`
183 | 
184 | Expected response:
185 | ```json
186 | {
187 |   "contents": [
188 |     {
189 |       "uri": "figma-node://abc123xyz789/1:1",
190 |       "title": "Page 1",
191 |       "description": "Type: CANVAS",
192 |       "text": "# Page 1\n\nType: CANVAS\nID: 1:1"
193 |     },
194 |     {
195 |       "uri": "figma-node://abc123xyz789/1:2",
196 |       "title": "Page 2",
197 |       "description": "Type: CANVAS",
198 |       "text": "# Page 2\n\nType: CANVAS\nID: 1:2"
199 |     }
200 |   ]
201 | }
202 | ```
203 | 
204 | ### Example: Accessing a Specific Node
205 | 
206 | Resource URI: `figma-node://abc123xyz789/123:456`
207 | 
208 | Expected response:
209 | ```json
210 | {
211 |   "contents": [{
212 |     "uri": "figma-node://abc123xyz789/123:456",
213 |     "title": "Header Text",
214 |     "description": "Type: TEXT",
215 |     "text": "# Header Text\n\nType: TEXT\nID: 123:456\nChildren: 0"
216 |   }]
217 | }
218 | ```
219 | 
220 | ## 5. Error Handling Examples
221 | 
222 | ### Example: File Not Found
223 | 
224 | ```json
225 | {
226 |   "content": [
227 |     { "type": "text", "text": "Error getting Figma file: File not found" }
228 |   ]
229 | }
230 | ```
231 | 
232 | ### Example: Node Not Found
233 | 
234 | ```json
235 | {
236 |   "content": [
237 |     { "type": "text", "text": "Node 123:456 not found in file abc123xyz789" }
238 |   ]
239 | }
240 | ```
241 | 
242 | ### Example: Authentication Error
243 | 
244 | ```json
245 | {
246 |   "content": [
247 |     { "type": "text", "text": "Error getting Figma file: Authentication failed. Please check your personal access token." }
248 |   ]
249 | }
250 | ```
251 | 
252 | ## 6. Tips and Best Practices
253 | 
254 | 1. **File Keys**: Obtain file keys from Figma file URLs. The format is typically `https://www.figma.com/file/FILE_KEY/FILE_NAME`.
255 | 
256 | 2. **Node IDs**: Node IDs can be found in Figma by right-clicking a layer and selecting "Copy/Paste as > Copy link". The node ID is the part after `?node-id=` in the URL.
257 | 
258 | 3. **Performance**: For large files, use targeted queries with specific node IDs rather than retrieving the entire file.
259 | 
260 | 4. **Image Export**: When exporting images, use appropriate scale factors: 1 for normal resolution, 2 for @2x, etc.
261 | 
262 | 5. **Comments**: When adding comments, provide node IDs to attach comments to specific elements.
263 | 
264 | 6. **Error Handling**: Always handle potential errors in your client application.
265 | 
266 | 7. **Resource Caching**: Consider caching resource responses for improved performance in your client application.
267 | 
```

--------------------------------------------------------------------------------
/src/plugin/creators/containerCreators.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Container element creation functions for Figma plugin
  3 |  * Including Frame, Component, and other container-like nodes
  4 |  */
  5 | 
  6 | import { createSolidPaint } from '../utils/colorUtils';
  7 | import { applyCommonProperties } from '../utils/nodeUtils';
  8 | 
  9 | /**
 10 |  * Create a frame from data
 11 |  * @param data Frame configuration data
 12 |  * @returns Created frame node
 13 |  */
 14 | export function createFrameFromData(data: any): FrameNode {
 15 |   const frame = figma.createFrame();
 16 |   
 17 |   // Size
 18 |   frame.resize(data.width || 100, data.height || 100);
 19 |   
 20 |   // Background
 21 |   if (data.fills) {
 22 |     frame.fills = data.fills;
 23 |   } else if (data.fill) {
 24 |     if (typeof data.fill === 'string') {
 25 |       frame.fills = [createSolidPaint(data.fill)];
 26 |     } else {
 27 |       frame.fills = [data.fill];
 28 |     }
 29 |   }
 30 |   
 31 |   // Auto layout properties
 32 |   if (data.layoutMode) frame.layoutMode = data.layoutMode;
 33 |   if (data.primaryAxisSizingMode) frame.primaryAxisSizingMode = data.primaryAxisSizingMode;
 34 |   if (data.counterAxisSizingMode) frame.counterAxisSizingMode = data.counterAxisSizingMode;
 35 |   if (data.primaryAxisAlignItems) frame.primaryAxisAlignItems = data.primaryAxisAlignItems;
 36 |   if (data.counterAxisAlignItems) frame.counterAxisAlignItems = data.counterAxisAlignItems;
 37 |   if (data.paddingLeft !== undefined) frame.paddingLeft = data.paddingLeft;
 38 |   if (data.paddingRight !== undefined) frame.paddingRight = data.paddingRight;
 39 |   if (data.paddingTop !== undefined) frame.paddingTop = data.paddingTop;
 40 |   if (data.paddingBottom !== undefined) frame.paddingBottom = data.paddingBottom;
 41 |   if (data.itemSpacing !== undefined) frame.itemSpacing = data.itemSpacing;
 42 |   
 43 |   // Corner radius
 44 |   if (data.cornerRadius !== undefined) frame.cornerRadius = data.cornerRadius;
 45 |   if (data.topLeftRadius !== undefined) frame.topLeftRadius = data.topLeftRadius;
 46 |   if (data.topRightRadius !== undefined) frame.topRightRadius = data.topRightRadius;
 47 |   if (data.bottomLeftRadius !== undefined) frame.bottomLeftRadius = data.bottomLeftRadius;
 48 |   if (data.bottomRightRadius !== undefined) frame.bottomRightRadius = data.bottomRightRadius;
 49 |   
 50 |   return frame;
 51 | }
 52 | 
 53 | /**
 54 |  * Create a component from data
 55 |  * @param data Component configuration data
 56 |  * @returns Created component node
 57 |  */
 58 | export function createComponentFromData(data: any): ComponentNode {
 59 |   const component = figma.createComponent();
 60 |   
 61 |   // Size
 62 |   component.resize(data.width || 100, data.height || 100);
 63 |   
 64 |   // Background
 65 |   if (data.fills) {
 66 |     component.fills = data.fills;
 67 |   } else if (data.fill) {
 68 |     if (typeof data.fill === 'string') {
 69 |       component.fills = [createSolidPaint(data.fill)];
 70 |     } else {
 71 |       component.fills = [data.fill];
 72 |     }
 73 |   }
 74 |   
 75 |   // Auto layout properties (components support same auto layout as frames)
 76 |   if (data.layoutMode) component.layoutMode = data.layoutMode;
 77 |   if (data.primaryAxisSizingMode) component.primaryAxisSizingMode = data.primaryAxisSizingMode;
 78 |   if (data.counterAxisSizingMode) component.counterAxisSizingMode = data.counterAxisSizingMode;
 79 |   if (data.primaryAxisAlignItems) component.primaryAxisAlignItems = data.primaryAxisAlignItems;
 80 |   if (data.counterAxisAlignItems) component.counterAxisAlignItems = data.counterAxisAlignItems;
 81 |   if (data.paddingLeft !== undefined) component.paddingLeft = data.paddingLeft;
 82 |   if (data.paddingRight !== undefined) component.paddingRight = data.paddingRight;
 83 |   if (data.paddingTop !== undefined) component.paddingTop = data.paddingTop;
 84 |   if (data.paddingBottom !== undefined) component.paddingBottom = data.paddingBottom;
 85 |   if (data.itemSpacing !== undefined) component.itemSpacing = data.itemSpacing;
 86 |   
 87 |   // Component properties
 88 |   if (data.description) component.description = data.description;
 89 |   
 90 |   return component;
 91 | }
 92 | 
 93 | /**
 94 |  * Create a group from data
 95 |  * Note: Groups require children, so this typically needs to be used after creating child nodes
 96 |  * 
 97 |  * @param data Group configuration data
 98 |  * @param children Child nodes to include in the group
 99 |  * @returns Created group node
100 |  */
101 | export function createGroupFromData(data: any, children: SceneNode[]): GroupNode {
102 |   // Create group with the provided children
103 |   const group = figma.group(children, figma.currentPage);
104 |   
105 |   // Apply common properties
106 |   applyCommonProperties(group, data);
107 |   
108 |   return group;
109 | }
110 | 
111 | /**
112 |  * Create an instance from data
113 |  * @param data Instance configuration data (must include componentId)
114 |  * @returns Created instance node
115 |  */
116 | export function createInstanceFromData(data: any): InstanceNode | null {
117 |   if (!data.componentId) {
118 |     console.error('Cannot create instance: componentId is required');
119 |     return null;
120 |   }
121 |   
122 |   // Try to find the component
123 |   const component = figma.getNodeById(data.componentId) as ComponentNode;
124 |   if (!component || component.type !== 'COMPONENT') {
125 |     console.error(`Cannot create instance: component with id ${data.componentId} not found`);
126 |     return null;
127 |   }
128 |   
129 |   // Create instance
130 |   const instance = component.createInstance();
131 |   
132 |   // Apply common properties
133 |   applyCommonProperties(instance, data);
134 |   
135 |   // Handle instance-specific properties
136 |   if (data.componentProperties) {
137 |     for (const [key, value] of Object.entries(data.componentProperties)) {
138 |       if (key in instance.componentProperties) {
139 |         // Handle different types of component properties
140 |         const prop = instance.componentProperties[key];
141 |         if (prop.type === 'BOOLEAN') {
142 |           instance.setProperties({ [key]: !!value });
143 |         } else if (prop.type === 'TEXT') {
144 |           instance.setProperties({ [key]: String(value) });
145 |         } else if (prop.type === 'INSTANCE_SWAP') {
146 |           instance.setProperties({ [key]: String(value) });
147 |         } else if (prop.type === 'VARIANT') {
148 |           instance.setProperties({ [key]: String(value) });
149 |         }
150 |       }
151 |     }
152 |   }
153 |   
154 |   return instance;
155 | }
156 | 
157 | /**
158 |  * Create a section from data
159 |  * Sections are a special type of node used to organize frames in Figma
160 |  * 
161 |  * @param data Section configuration data
162 |  * @returns Created section node
163 |  */
164 | export function createSectionFromData(data: any): SectionNode {
165 |   const section = figma.createSection();
166 |   
167 |   // Section-specific properties
168 |   if (data.name) section.name = data.name;
169 |   if (data.sectionContentsHidden !== undefined) section.sectionContentsHidden = data.sectionContentsHidden;
170 |   
171 |   // Apply common properties that apply to sections
172 |   if (data.x !== undefined) section.x = data.x;
173 |   if (data.y !== undefined) section.y = data.y;
174 |   
175 |   return section;
176 | } 
```

--------------------------------------------------------------------------------
/src/plugin/creators/elementCreator.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Universal element creator for Figma plugin
  3 |  * Acts as a central entry point for creating any type of Figma element
  4 |  */
  5 | 
  6 | import { 
  7 |   createRectangleFromData, 
  8 |   createEllipseFromData,
  9 |   createPolygonFromData,
 10 |   createStarFromData,
 11 |   createLineFromData,
 12 |   createVectorFromData
 13 | } from './shapeCreators';
 14 | 
 15 | import {
 16 |   createFrameFromData,
 17 |   createComponentFromData,
 18 |   createInstanceFromData,
 19 |   createGroupFromData,
 20 |   createSectionFromData
 21 | } from './containerCreators';
 22 | 
 23 | import { createTextFromData } from './textCreator';
 24 | 
 25 | import {
 26 |   createBooleanOperationFromData,
 27 |   createConnectorFromData,
 28 |   createShapeWithTextFromData,
 29 |   createCodeBlockFromData,
 30 |   createTableFromData,
 31 |   createWidgetFromData,
 32 |   createMediaFromData
 33 | } from './specialCreators';
 34 | 
 35 | import {
 36 |   createImageFromData,
 37 |   createImageFromBytesAsync,
 38 |   createGifFromData,
 39 |   createVideoFromDataAsync,
 40 |   createLinkPreviewFromDataAsync
 41 | } from './imageCreators';
 42 | 
 43 | import {
 44 |   createSliceFromData,
 45 |   createPageFromData,
 46 |   createPageDividerFromData,
 47 |   createSlideFromData,
 48 |   createSlideRowFromData
 49 | } from './sliceCreators';
 50 | 
 51 | import {
 52 |   createComponentFromNodeData,
 53 |   createComponentSetFromData
 54 | } from './componentCreators';
 55 | 
 56 | import { applyCommonProperties, selectAndFocusNodes } from '../utils/nodeUtils';
 57 | 
 58 | /**
 59 |  * Unified create element function that works with structured data
 60 |  * Detects the type of element to create based on the data.type property
 61 |  * 
 62 |  * @param data Configuration data with type and other properties
 63 |  * @returns Created Figma node or null if creation failed
 64 |  */
 65 | export async function createElementFromData(data: any): Promise<SceneNode | null> {
 66 |   if (!data || !data.type) {
 67 |     console.error('Invalid element data: missing type');
 68 |     return null;
 69 |   }
 70 |   
 71 |   let element: SceneNode | null = null;
 72 |   
 73 |   try {
 74 |     // Create the element based on its type
 75 |     switch (data.type.toLowerCase()) {
 76 |       // Basic shapes
 77 |       case 'rectangle':
 78 |         element = createRectangleFromData(data);
 79 |         break;
 80 |         
 81 |       case 'ellipse':
 82 |       case 'circle':
 83 |         element = createEllipseFromData(data);
 84 |         break;
 85 |         
 86 |       case 'polygon':
 87 |         element = createPolygonFromData(data);
 88 |         break;
 89 |         
 90 |       case 'star':
 91 |         element = createStarFromData(data);
 92 |         break;
 93 |         
 94 |       case 'line':
 95 |         element = createLineFromData(data);
 96 |         break;
 97 |         
 98 |       case 'vector':
 99 |         element = createVectorFromData(data);
100 |         break;
101 |       
102 |       // Container elements
103 |       case 'frame':
104 |         element = createFrameFromData(data);
105 |         break;
106 |         
107 |       case 'component':
108 |         element = createComponentFromData(data);
109 |         break;
110 |         
111 |       case 'componentfromnode':
112 |         element = createComponentFromNodeData(data);
113 |         break;
114 |         
115 |       case 'componentset':
116 |         element = createComponentSetFromData(data);
117 |         break;
118 |         
119 |       case 'instance':
120 |         element = createInstanceFromData(data);
121 |         break;
122 |         
123 |       case 'section':
124 |         element = createSectionFromData(data);
125 |         break;
126 |         
127 |       // Text
128 |       case 'text':
129 |         element = await createTextFromData(data);
130 |         break;
131 |         
132 |       // Special types
133 |       case 'boolean':
134 |       case 'booleanoperation':
135 |         element = createBooleanOperationFromData(data);
136 |         break;
137 |         
138 |       case 'connector':
139 |         element = createConnectorFromData(data);
140 |         break;
141 |         
142 |       case 'shapewithtext':
143 |         element = createShapeWithTextFromData(data);
144 |         break;
145 |         
146 |       case 'codeblock':
147 |         element = createCodeBlockFromData(data);
148 |         break;
149 |         
150 |       case 'table':
151 |         element = createTableFromData(data);
152 |         break;
153 |         
154 |       case 'widget':
155 |         element = createWidgetFromData(data);
156 |         break;
157 |         
158 |       case 'media':
159 |         element = createMediaFromData(data);
160 |         break;
161 |         
162 |       // Image and media types
163 |       case 'image':
164 |         if (data.bytes || data.file) {
165 |           element = await createImageFromBytesAsync(data);
166 |         } else {
167 |           element = createImageFromData(data);
168 |         }
169 |         break;
170 |         
171 |       case 'gif':
172 |         element = createGifFromData(data);
173 |         break;
174 |         
175 |       case 'video':
176 |         element = await createVideoFromDataAsync(data);
177 |         break;
178 |         
179 |       case 'linkpreview':
180 |         element = await createLinkPreviewFromDataAsync(data);
181 |         break;
182 |         
183 |       // Page and slice types
184 |       case 'slice':
185 |         element = createSliceFromData(data);
186 |         break;
187 |         
188 |       case 'page':
189 |         // PageNode is not a SceneNode in Figma's type system
190 |         // So we create it but don't return it through the same path
191 |         const page = createPageFromData(data);
192 |         console.log(`Created page: ${page.name}`);
193 |         // We return null as we can't return a PageNode as SceneNode
194 |         return null;
195 |         
196 |       case 'pagedivider':
197 |         element = createPageDividerFromData(data);
198 |         break;
199 |         
200 |       case 'slide':
201 |         element = createSlideFromData(data);
202 |         break;
203 |         
204 |       case 'sliderow':
205 |         element = createSlideRowFromData(data);
206 |         break;
207 |         
208 |       // Special cases
209 |       case 'group':
210 |         if (!data.children || !Array.isArray(data.children) || data.children.length < 1) {
211 |           console.error('Cannot create group: children array is required');
212 |           return null;
213 |         }
214 |         
215 |         // Create all child elements first
216 |         const childNodes: SceneNode[] = [];
217 |         for (const childData of data.children) {
218 |           const child = await createElementFromData(childData);
219 |           if (child) childNodes.push(child);
220 |         }
221 |         
222 |         if (childNodes.length > 0) {
223 |           element = createGroupFromData(data, childNodes);
224 |         } else {
225 |           console.error('Cannot create group: no valid children were created');
226 |           return null;
227 |         }
228 |         break;
229 |         
230 |       default:
231 |         console.error(`Unsupported element type: ${data.type}`);
232 |         return null;
233 |     }
234 |     
235 |     // Apply common properties if element was created
236 |     if (element) {
237 |       applyCommonProperties(element, data);
238 |       
239 |       // Select and focus on the element if requested
240 |       if (data.select !== false) {
241 |         selectAndFocusNodes(element);
242 |       }
243 |     }
244 |     
245 |     return element;
246 |   } catch (error) {
247 |     console.error(`Error creating element: ${error instanceof Error ? error.message : 'Unknown error'}`);
248 |     return null;
249 |   }
250 | }
251 | 
252 | /**
253 |  * Create multiple elements from an array of data objects
254 |  * @param dataArray Array of element configuration data
255 |  * @returns Array of created nodes
256 |  */
257 | export async function createElementsFromDataArray(dataArray: any[]): Promise<SceneNode[]> {
258 |   const createdNodes: SceneNode[] = [];
259 |   
260 |   for (const data of dataArray) {
261 |     const node = await createElementFromData(data);
262 |     if (node) createdNodes.push(node);
263 |   }
264 |   
265 |   // If there are created nodes, select them all at the end
266 |   if (createdNodes.length > 0) {
267 |     selectAndFocusNodes(createdNodes);
268 |   }
269 |   
270 |   return createdNodes;
271 | } 
```

--------------------------------------------------------------------------------
/src/tools/page.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Page tools for the Figma MCP server
  3 |  */
  4 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
  5 | import { z } from "zod";
  6 | import {
  7 |   isPluginConnected,
  8 |   sendCommandToPlugin,
  9 | } from "../services/websocket.js";
 10 | 
 11 | // Define interfaces for page data
 12 | interface PageData {
 13 |   id: string;
 14 |   name: string;
 15 |   children?: any[];
 16 |   [key: string]: any;
 17 | }
 18 | 
 19 | export const getPagesTool = (server: McpServer) => {
 20 |   server.tool("get_pages", {}, async () => {
 21 |     try {
 22 |       // Get pages using WebSocket only if plugin is connected
 23 |       if (!isPluginConnected()) {
 24 |         return {
 25 |           content: [
 26 |             {
 27 |               type: "text",
 28 |               text: "No Figma plugin is connected. Please make sure the Figma plugin is running and connected to the MCP server.",
 29 |             },
 30 |           ],
 31 |         };
 32 |       }
 33 | 
 34 |       const response = await sendCommandToPlugin("get-pages", {});
 35 | 
 36 |       if (!response.success) {
 37 |         throw new Error(response.error || "Failed to get pages");
 38 |       }
 39 | 
 40 |       // Process the response to handle different result formats
 41 |       const result = response.result || {};
 42 |       const pages = result.items || [];
 43 |       const pagesCount = result.count || 0;
 44 | 
 45 |       // Check if we have pages to display
 46 |       if (pagesCount === 0 && !pages.length) {
 47 |         return {
 48 |           content: [
 49 |             { type: "text", text: `# Pages in Figma File` },
 50 |             { type: "text", text: `No pages found in the current Figma file.` },
 51 |           ],
 52 |         };
 53 |       }
 54 | 
 55 |       return {
 56 |         content: [
 57 |           { type: "text", text: `# Pages in Figma File` },
 58 |           { type: "text", text: `Found ${pagesCount || pages.length} pages:` },
 59 |           {
 60 |             type: "text",
 61 |             text: pages
 62 |               .map((page: PageData) => `- ${page.name} (ID: ${page.id})`)
 63 |               .join("\n"),
 64 |           },
 65 |         ],
 66 |       };
 67 |     } catch (error) {
 68 |       console.error("Error fetching pages:", error);
 69 |       return {
 70 |         content: [
 71 |           {
 72 |             type: "text",
 73 |             text: `Error getting pages: ${(error as Error).message}`,
 74 |           },
 75 |         ],
 76 |       };
 77 |     }
 78 |   });
 79 | };
 80 | 
 81 | export const getPageTool = (server: McpServer) => {
 82 |   server.tool(
 83 |     "get_page",
 84 |     {
 85 |       page_id: z.string().min(1).describe("The ID of the page to retrieve").optional(),
 86 |     },
 87 |     async ({ page_id }) => {
 88 |       try {
 89 |         // Get page using WebSocket only if plugin is connected
 90 |         if (!isPluginConnected()) {
 91 |           return {
 92 |             content: [
 93 |               {
 94 |                 type: "text",
 95 |                 text: "No Figma plugin is connected. Please make sure the Figma plugin is running and connected to the MCP server.",
 96 |               },
 97 |             ],
 98 |           };
 99 |         }
100 | 
101 |         // If page_id is not provided, get the current page
102 |         const response = await sendCommandToPlugin("get-page", {
103 |           page_id,
104 |         });
105 | 
106 |         if (!response.success) {
107 |           throw new Error(response.error || "Failed to get page");
108 |         }
109 | 
110 |         const pageNode = response.result;
111 | 
112 |         return {
113 |           content: [
114 |             { type: "text", text: `# Page: ${pageNode.name}` },
115 |             { type: "text", text: `ID: ${pageNode.id}` },
116 |             { type: "text", text: `Type: ${pageNode.type}` },
117 |             {
118 |               type: "text",
119 |               text: `Elements: ${pageNode.children?.length || 0}`,
120 |             },
121 |             {
122 |               type: "text",
123 |               text: "```json\n" + JSON.stringify(pageNode, null, 2) + "\n```",
124 |             },
125 |           ],
126 |         };
127 |       } catch (error) {
128 |         console.error("Error fetching page:", error);
129 |         return {
130 |           content: [
131 |             {
132 |               type: "text",
133 |               text: `Error getting page: ${(error as Error).message}`,
134 |             },
135 |           ],
136 |         };
137 |       }
138 |     }
139 |   );
140 | };
141 | 
142 | export const createPageTool = (server: McpServer) => {
143 |   server.tool(
144 |     "create_page",
145 |     {
146 |       page_name: z.string().min(1).describe("Name for the new page"),
147 |     },
148 |     async ({ page_name }) => {
149 |       try {
150 |         if (!isPluginConnected()) {
151 |           return {
152 |             content: [
153 |               {
154 |                 type: "text",
155 |                 text: "No Figma plugin is connected. Please make sure the Figma plugin is running and connected to the MCP server.",
156 |               },
157 |             ],
158 |           };
159 |         }
160 | 
161 |         // Use WebSocket to send command to plugin
162 |         const response = await sendCommandToPlugin("create-page", {
163 |           name: page_name,
164 |         });
165 | 
166 |         if (!response.success) {
167 |           throw new Error(response.error || "Failed to create page");
168 |         }
169 | 
170 |         return {
171 |           content: [
172 |             {
173 |               type: "text",
174 |               text: `# Page Created Successfully`,
175 |             },
176 |             {
177 |               type: "text",
178 |               text: `A new page named "${page_name}" has been created.`,
179 |             },
180 |             {
181 |               type: "text",
182 |               text:
183 |                 response.result && response.result.id
184 |                   ? `Page ID: ${response.result.id}`
185 |                   : `Creation successful`,
186 |             },
187 |           ],
188 |         };
189 |       } catch (error) {
190 |         console.error("Error creating page:", error);
191 |         return {
192 |           content: [
193 |             {
194 |               type: "text",
195 |               text: `Error creating page: ${(error as Error).message}`,
196 |             },
197 |           ],
198 |         };
199 |       }
200 |     }
201 |   );
202 | };
203 | 
204 | export const switchPageTool = (server: McpServer) => {
205 |   server.tool(
206 |     "switch_page",
207 |     {
208 |       page_id: z.string().min(1).describe("The ID of the page to switch to"),
209 |     },
210 |     async ({ page_id }) => {
211 |       try {
212 |         if (!isPluginConnected()) {
213 |           return {
214 |             content: [
215 |               {
216 |                 type: "text",
217 |                 text: "No Figma plugin is connected. Please make sure the Figma plugin is running and connected to the MCP server.",
218 |               },
219 |             ],
220 |           };
221 |         }
222 | 
223 |         // Use WebSocket to send command to plugin
224 |         const response = await sendCommandToPlugin("switch-page", {
225 |           id: page_id, // Note: plugin expects 'id', not 'page_id'
226 |         });
227 | 
228 |         if (!response.success) {
229 |           throw new Error(response.error || "Failed to switch page");
230 |         }
231 | 
232 |         return {
233 |           content: [
234 |             {
235 |               type: "text",
236 |               text: `# Page Switched Successfully`,
237 |             },
238 |             {
239 |               type: "text",
240 |               text: `Successfully switched to page with ID: ${page_id}`,
241 |             },
242 |             {
243 |               type: "text",
244 |               text:
245 |                 response.result && response.result.name
246 |                   ? `Current page: ${response.result.name}`
247 |                   : `Switch successful`,
248 |             },
249 |           ],
250 |         };
251 |       } catch (error) {
252 |         console.error("Error switching page:", error);
253 |         return {
254 |           content: [
255 |             {
256 |               type: "text",
257 |               text: `Error switching page: ${(error as Error).message}`,
258 |             },
259 |           ],
260 |         };
261 |       }
262 |     }
263 |   );
264 | };
265 | 
266 | /**
267 |  * Registers all page-related tools with the MCP server
268 |  */
269 | export const registerPageTools = (server: McpServer): void => {
270 |   getPagesTool(server);
271 |   getPageTool(server);
272 |   createPageTool(server);
273 |   switchPageTool(server);
274 | };
275 | 
```

--------------------------------------------------------------------------------
/src/plugin/creators/textCreator.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Text element creation functions for Figma plugin
  3 |  */
  4 | 
  5 | import { createSolidPaint } from '../utils/colorUtils';
  6 | import { selectAndFocusNodes } from '../utils/nodeUtils';
  7 | 
  8 | /**
  9 |  * Create a text node from data
 10 |  * @param data Text configuration data
 11 |  * @returns Created text node
 12 |  */
 13 | export async function createTextFromData(data: any): Promise<TextNode> {
 14 |   const text = figma.createText();
 15 |   
 16 |   // Load font - default to Inter if not specified
 17 |   const fontFamily = data.fontFamily || (data.fontName.family) || "Inter";
 18 |   const fontStyle = data.fontStyle || (data.fontName.style) || "Regular";
 19 |   
 20 |   // Load the font before setting text
 21 |   try {
 22 |     await figma.loadFontAsync({ family: fontFamily, style: fontStyle });
 23 |   } catch (error) {
 24 |     console.warn(`Failed to load font ${fontFamily} ${fontStyle}. Falling back to Inter Regular.`);
 25 |     await figma.loadFontAsync({ family: "Inter", style: "Regular" });
 26 |   }
 27 |   
 28 |   // Set basic text content
 29 |   text.characters = data.text || data.characters || "Text";
 30 |   
 31 |   // Position and size
 32 |   if (data.x !== undefined) text.x = data.x;
 33 |   if (data.y !== undefined) text.y = data.y;
 34 |   
 35 |   // Text size and dimensions
 36 |   if (data.fontSize) text.fontSize = data.fontSize;
 37 |   if (data.width) text.resize(data.width, text.height);
 38 |   
 39 |   // Text style and alignment
 40 |   if (data.fontName) text.fontName = data.fontName;
 41 |   if (data.textAlignHorizontal) text.textAlignHorizontal = data.textAlignHorizontal;
 42 |   if (data.textAlignVertical) text.textAlignVertical = data.textAlignVertical;
 43 |   if (data.textAutoResize) text.textAutoResize = data.textAutoResize;
 44 |   if (data.textTruncation) text.textTruncation = data.textTruncation;
 45 |   if (data.maxLines !== undefined) text.maxLines = data.maxLines;
 46 |   
 47 |   // Paragraph styling
 48 |   if (data.paragraphIndent) text.paragraphIndent = data.paragraphIndent;
 49 |   if (data.paragraphSpacing) text.paragraphSpacing = data.paragraphSpacing;
 50 |   if (data.listSpacing) text.listSpacing = data.listSpacing;
 51 |   if (data.hangingPunctuation !== undefined) text.hangingPunctuation = data.hangingPunctuation;
 52 |   if (data.hangingList !== undefined) text.hangingList = data.hangingList;
 53 |   if (data.autoRename !== undefined) text.autoRename = data.autoRename;
 54 |   
 55 |   // Text styling
 56 |   if (data.letterSpacing) text.letterSpacing = data.letterSpacing;
 57 |   if (data.lineHeight) text.lineHeight = data.lineHeight;
 58 |   if (data.leadingTrim) text.leadingTrim = data.leadingTrim;
 59 |   if (data.textCase) text.textCase = data.textCase;
 60 |   if (data.textDecoration) text.textDecoration = data.textDecoration;
 61 |   if (data.textStyleId) text.textStyleId = data.textStyleId;
 62 |   
 63 |   // Text decoration details
 64 |   if (data.textDecorationStyle) text.textDecorationStyle = data.textDecorationStyle;
 65 |   if (data.textDecorationOffset) text.textDecorationOffset = data.textDecorationOffset;
 66 |   if (data.textDecorationThickness) text.textDecorationThickness = data.textDecorationThickness;
 67 |   if (data.textDecorationColor) text.textDecorationColor = data.textDecorationColor;
 68 |   if (data.textDecorationSkipInk !== undefined) text.textDecorationSkipInk = data.textDecorationSkipInk;
 69 |   
 70 |   // Text fill
 71 |   if (data.fills) {
 72 |     text.fills = data.fills;
 73 |   } else if (data.fill) {
 74 |     if (typeof data.fill === 'string') {
 75 |       text.fills = [createSolidPaint(data.fill)];
 76 |     } else {
 77 |       text.fills = [data.fill];
 78 |     }
 79 |   }
 80 |   
 81 |   // Text hyperlink
 82 |   if (data.hyperlink) {
 83 |     text.hyperlink = data.hyperlink;
 84 |   }
 85 |   
 86 |   // Layout properties
 87 |   if (data.layoutAlign) text.layoutAlign = data.layoutAlign;
 88 |   if (data.layoutGrow !== undefined) text.layoutGrow = data.layoutGrow;
 89 |   if (data.layoutSizingHorizontal) text.layoutSizingHorizontal = data.layoutSizingHorizontal;
 90 |   if (data.layoutSizingVertical) text.layoutSizingVertical = data.layoutSizingVertical;
 91 |   
 92 |   // Apply text range styles if provided
 93 |   if (data.rangeStyles && Array.isArray(data.rangeStyles)) {
 94 |     applyTextRangeStyles(text, data.rangeStyles);
 95 |   }
 96 |   
 97 |   // Apply common base properties
 98 |   if (data.name) text.name = data.name;
 99 |   if (data.visible !== undefined) text.visible = data.visible;
100 |   if (data.locked !== undefined) text.locked = data.locked;
101 |   if (data.opacity !== undefined) text.opacity = data.opacity;
102 |   if (data.blendMode) text.blendMode = data.blendMode;
103 |   if (data.effects) text.effects = data.effects;
104 |   if (data.effectStyleId) text.effectStyleId = data.effectStyleId;
105 |   if (data.exportSettings) text.exportSettings = data.exportSettings;
106 |   if (data.constraints) text.constraints = data.constraints;
107 |   
108 |   return text;
109 | }
110 | 
111 | /**
112 |  * Create a simple text node with basic properties
113 |  * @param x X coordinate
114 |  * @param y Y coordinate
115 |  * @param content Text content
116 |  * @param fontSize Font size
117 |  * @returns Created text node
118 |  */
119 | export async function createText(x: number, y: number, content: string, fontSize: number): Promise<TextNode> {
120 |   // Use the data-driven function
121 |   const text = await createTextFromData({
122 |     text: content,
123 |     fontSize,
124 |     x,
125 |     y
126 |   });
127 |   
128 |   // Select and focus
129 |   selectAndFocusNodes(text);
130 |   
131 |   return text;
132 | }
133 | 
134 | /**
135 |  * Apply character-level styling to text ranges in a text node
136 |  * @param textNode Text node to style
137 |  * @param ranges Array of range objects with start, end, and style properties
138 |  */
139 | export function applyTextRangeStyles(textNode: TextNode, ranges: Array<{start: number, end: number, style: any}>): void {
140 |   for (const range of ranges) {
141 |     // Apply individual style properties to the range
142 |     for (const [property, value] of Object.entries(range.style)) {
143 |       if (property === 'fills') {
144 |         textNode.setRangeFills(range.start, range.end, value as Paint[]);
145 |       } else if (property === 'fillStyleId') {
146 |         textNode.setRangeFillStyleId(range.start, range.end, value as string);
147 |       } else if (property === 'fontName') {
148 |         textNode.setRangeFontName(range.start, range.end, value as FontName);
149 |       } else if (property === 'fontSize') {
150 |         textNode.setRangeFontSize(range.start, range.end, value as number);
151 |       } else if (property === 'textCase') {
152 |         textNode.setRangeTextCase(range.start, range.end, value as TextCase);
153 |       } else if (property === 'textDecoration') {
154 |         textNode.setRangeTextDecoration(range.start, range.end, value as TextDecoration);
155 |       } else if (property === 'textDecorationStyle') {
156 |         textNode.setRangeTextDecorationStyle(range.start, range.end, value as TextDecorationStyle);
157 |       } else if (property === 'textDecorationOffset') {
158 |         textNode.setRangeTextDecorationOffset(range.start, range.end, value as TextDecorationOffset);
159 |       } else if (property === 'textDecorationThickness') {
160 |         textNode.setRangeTextDecorationThickness(range.start, range.end, value as TextDecorationThickness);
161 |       } else if (property === 'textDecorationColor') {
162 |         textNode.setRangeTextDecorationColor(range.start, range.end, value as TextDecorationColor);
163 |       } else if (property === 'textDecorationSkipInk') {
164 |         textNode.setRangeTextDecorationSkipInk(range.start, range.end, value as boolean);
165 |       } else if (property === 'letterSpacing') {
166 |         textNode.setRangeLetterSpacing(range.start, range.end, value as LetterSpacing);
167 |       } else if (property === 'lineHeight') {
168 |         textNode.setRangeLineHeight(range.start, range.end, value as LineHeight);
169 |       } else if (property === 'hyperlink') {
170 |         textNode.setRangeHyperlink(range.start, range.end, value as HyperlinkTarget);
171 |       } else if (property === 'textStyleId') {
172 |         textNode.setRangeTextStyleId(range.start, range.end, value as string);
173 |       } else if (property === 'indentation') {
174 |         textNode.setRangeIndentation(range.start, range.end, value as number);
175 |       } else if (property === 'paragraphIndent') {
176 |         textNode.setRangeParagraphIndent(range.start, range.end, value as number);
177 |       } else if (property === 'paragraphSpacing') {
178 |         textNode.setRangeParagraphSpacing(range.start, range.end, value as number);
179 |       } else if (property === 'listOptions') {
180 |         textNode.setRangeListOptions(range.start, range.end, value as TextListOptions);
181 |       } else if (property === 'listSpacing') {
182 |         textNode.setRangeListSpacing(range.start, range.end, value as number);
183 |       }
184 |     }
185 |   }
186 | } 
```
Page 1/3FirstPrevNextLast