#
tokens: 47370/50000 58/63 files (page 1/2)
lines: off (toggle) GitHub
raw markdown copy
This is page 1 of 2. Use http://codebase.md/sichang824/mcp-figma?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:
--------------------------------------------------------------------------------

```
dotenv
```

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

```
11949

```

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

```
# Figma API credentials
FIGMA_PERSONAL_ACCESS_TOKEN=your_figma_token_here

# Server configuration
PORT=3001
NODE_ENV=development

```

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

```
# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore

# Logs

logs
_.log
npm-debug.log_
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*

# Caches

.cache

# Diagnostic reports (https://nodejs.org/api/report.html)

report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json

# Runtime data

pids
_.pid
_.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover

lib-cov

# Coverage directory used by tools like istanbul

coverage
*.lcov

# nyc test coverage

.nyc_output

# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)

.grunt

# Bower dependency directory (https://bower.io/)

bower_components

# node-waf configuration

.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)

build/Release

# Dependency directories

node_modules/
jspm_packages/

# Snowpack dependency directory (https://snowpack.dev/)

web_modules/

# TypeScript cache

*.tsbuildinfo

# Optional npm cache directory

.npm

# Optional eslint cache

.eslintcache

# Optional stylelint cache

.stylelintcache

# Microbundle cache

.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/

# Optional REPL history

.node_repl_history

# Output of 'npm pack'

*.tgz

# Yarn Integrity file

.yarn-integrity

# dotenv environment variable files

.env
.env.development.local
.env.test.local
.env.production.local
.env.local

# parcel-bundler cache (https://parceljs.org/)

.parcel-cache

# Next.js build output

.next
out

# Nuxt.js build / generate output

.nuxt
dist

# Gatsby files

# Comment in the public line in if your project uses Gatsby and not Next.js

# https://nextjs.org/blog/next-9-1#public-directory-support

# public

# vuepress build output

.vuepress/dist

# vuepress v2.x temp and cache directory

.temp

# Docusaurus cache and generated files

.docusaurus

# Serverless directories

.serverless/

# FuseBox cache

.fusebox/

# DynamoDB Local files

.dynamodb/

# TernJS port file

.tern-port

# Stores VSCode versions used for testing VSCode extensions

.vscode-test

# yarn v2

.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*

# IntelliJ based IDEs
.idea

# Finder (MacOS) folder config
.DS_Store
.env.test

# Environment files
.env.*
!.env.example

# Dependencies
node_modules/
.npm
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Build output
dist/
build/

# OS specific files
.DS_Store
Thumbs.db

# IDE specific files
.idea/
.vscode/
*.sublime-*
.history/

```

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

```markdown
# Widget Tools for Figma MCP

This directory contains tools for interacting with Figma widgets through the MCP server.

## Directory Structure

Each tool is organized in its own directory:

- `get-widgets`: Lists all widgets in a file
- `get-widget`: Gets detailed information about a specific widget
- `get-widget-sync-data`: Retrieves a widget's synchronized state data
- `search-widgets`: Searches for widgets with specific properties
- `analyze-widget-structure`: Provides detailed analysis of a widget's structure

## Adding New Widget Tools

To add a new widget tool:

1. Create a new directory for your tool under `src/tools/widget/`
2. Create an `index.ts` file with your tool implementation
3. Update the main `index.ts` to import and register your tool

## Using Shared Utilities

Common widget utilities can be found in `src/tools/utils/widget-utils.ts`.

## Widget Tool Pattern

Each widget tool follows this pattern:

```typescript
export const yourToolName = (server: McpServer) => {
  server.tool(
    "tool_name",
    {
      // Parameter schema using zod
      param1: z.string().describe("Description"),
      param2: z.number().describe("Description")
    },
    async ({ param1, param2 }) => {
      try {
        // Tool implementation
        return {
          content: [
            { type: "text", text: "Response content" }
          ]
        };
      } catch (error) {
        console.error('Error message:', error);
        return {
          content: [
            { type: "text", text: `Error: ${(error as Error).message}` }
          ]
        };
      }
    }
  );
};
```

For more information, see the [Widget Tools Guide](/docs/widget-tools-guide.md).

```

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

```markdown
# Figma MCP Server Documentation

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.

## Table of Contents

1. [Project Overview](./01-overview.md)
   - Introduction and purpose
   - Core features
   - Technology stack
   - Project structure
   - Integration with AI systems

2. [Implementation Steps](./02-implementation-steps.md)
   - Project setup
   - Configuration
   - Figma API integration
   - MCP server implementation
   - Build system
   - Documentation
   - Testing and verification

3. [Components and Features](./03-components-and-features.md)
   - Core components
   - MCP tools
   - Resource templates
   - Error handling
   - Response formatting

4. [Usage Guide](./04-usage-guide.md)
   - Setup instructions
   - Running the server
   - Using MCP tools (with examples)
   - Using resource templates
   - Error handling examples
   - Tips and best practices

5. [Project Status and Roadmap](./05-project-status.md)
   - Current status
   - Next steps
   - Version history
   - Known issues
   - Contribution guidelines
   - Support and feedback

## Quick Start

To get started with the Figma MCP server:

1. Install dependencies:
   ```bash
   make install
   ```

2. Configure your Figma API token in `.env`

3. Start the server:
   ```bash
   make mcp
   ```

For more detailed instructions, see the [Usage Guide](./04-usage-guide.md).

## Project Status

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.

## Contributing

Contributions are welcome! See the [Contribution Guidelines](./05-project-status.md#5-contribution-guidelines) for more information on how to contribute to the project.

---

Last updated: April 13, 2025

```

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

```markdown
[![MSeeP.ai Security Assessment Badge](https://mseep.net/pr/sichang824-mcp-figma-badge.png)](https://mseep.ai/app/sichang824-mcp-figma)

# Figma MCP Server

A Figma API server implementation based on Model Context Protocol (MCP), supporting Figma plugin and widget integration.

## Features

- Interact with Figma API through MCP
- WebSocket server for Figma plugin communication
- Support for Figma widget development
- Environment variable configuration via command line arguments
- Rich set of Figma operation tools

## Installation

1. Clone the repository:

```bash
git clone <repository-url>
cd figma-mcp
```

2. Install dependencies:

```bash
bun install
```

## Configuration

### Environment Variables

Create a `.env` file and set the following environment variables:

```
FIGMA_PERSONAL_ACCESS_TOKEN=your_figma_token
PORT=3001
NODE_ENV=development
```

### Getting a Figma Access Token

1. Log in to [Figma](https://www.figma.com/)
2. Go to Account Settings > Personal Access Tokens
3. Create a new access token
4. Copy the token to your `.env` file or pass it via command line arguments

## Usage

### Build the Project

```bash
bun run build
```

### Run the Development Server

```bash
bun run dev
```

### Using Command Line Arguments

Support for setting environment variables via the `-e` parameter:

```bash
bun --watch src/index.ts -e FIGMA_PERSONAL_ACCESS_TOKEN=your_token -e PORT=6000
```

You can also use a dedicated token parameter:

```bash
bun --watch src/index.ts --token your_token
```

Or its shorthand:

```bash
bun --watch src/index.ts -t your_token
```

## Configuring MCP in Cursor

Add to the `.cursor/mcp.json` file:

```json
{
  "Figma MCP": {
    "command": "bun",
    "args": [
      "--watch",
      "/path/to/figma-mcp/src/index.ts",
      "-e",
      "FIGMA_PERSONAL_ACCESS_TOKEN=your_token_here",
      "-e",
      "PORT=6000"
    ]
  }
}
```

## Available Tools

The server provides the following Figma operation tools:

- File operations: Get files, versions, etc.
- Node operations: Get and manipulate Figma nodes
- Comment operations: Manage comments in Figma files
- Image operations: Export Figma elements as images
- Search functionality: Search content in Figma files
- Component operations: Manage Figma components
- Canvas operations: Create rectangles, circles, text, etc.
- Widget operations: Manage Figma widgets

## Figma Plugin Development

### Plugin Introduction

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.

### Building and Testing

Build the plugin:

```bash
bun run build:plugin
```

Run in development mode:

```bash
bun run dev:plugin
```

### Loading the Plugin in Figma

![image](./docs/image.png)

1. Right-click in Figma to open the menu -> Plugins -> Development -> Import plugin from manifest...
2. Select the plugin's `manifest.json` file
3. Your plugin will now appear in Figma's plugin menu

### Plugin Interaction with MCP Server

Plugins can communicate with the MCP server via WebSocket to achieve:

- Complex data processing
- External API integration
- Cross-session data persistence
- AI functionality integration

## Development

### Build Widget

```bash
bun run build:widget
```

### Build Plugin

```bash
bun run build:plugin
```

### Development Mode

```bash
bun run dev:widget  # Widget development mode
bun run dev:plugin  # Plugin development mode
```

## License

MIT

```

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

```typescript

```

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

```json
{
  "name": "Figma MCP Canvas Operation Tool",
  "id": "figma-mcp-canvas-tools",
  "api": "1.0.0",
  "main": "code.js",
  "ui": "ui.html",
  "editorType": ["figma"],
  "permissions": []
} 
```

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

```json
{
  "name": "MCP Figma Widget",
  "id": "mcp-figma-widget",
  "api": "1.0.0",
  "main": "dist/widget-code.js",
  "capabilities": ["network-access"],
  "editorType": ["figma"],
  "containsWidget": true,
  "widgetApi": "1.0.0"
}

```

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

```json
{
  "compilerOptions": {
    "target": "es6",
    "lib": ["es6", "dom"],
    "typeRoots": ["../../node_modules/@types", "../../node_modules/@figma"],
    "moduleResolution": "node",
    "strict": true
  },
  "include": ["*.ts", "utils/**/*.ts"],
  "exclude": ["node_modules"]
} 
```

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

```json
{
  "compilerOptions": {
    "jsx": "react",
    "jsxFactory": "figma.widget.h",
    "jsxFragmentFactory": "figma.widget.Fragment",
    "target": "es6",
    "strict": true,
    "typeRoots": [
      "./node_modules/@types",
      "./node_modules/@figma"
    ]
  },
  "include": ["src/widget/**/*"],
  "exclude": ["node_modules", "dist"]
}

```

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

```json
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true,
    "resolveJsonModule": true,
    "outDir": "dist",
    "declaration": true,
    "declarationDir": "dist/types",
    "baseUrl": ".",
    "paths": {
      "~/*": ["src/*"]
    }
  },
  "include": ["src/**/*", "types/**/*"],
  "exclude": ["node_modules", "dist"]
}

```

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

```typescript
/**
 * Utility functions for Figma MCP Server
 */

/**
 * Log function that writes to stderr instead of stdout
 * to avoid interfering with MCP stdio communication
 */
export function log(message: string): void {
  process.stderr.write(`${message}\n`);
}

/**
 * Error log function that writes to stderr
 */
export function logError(message: string, error?: unknown): void {
  const errorMessage = error instanceof Error 
    ? error.message 
    : error ? String(error) : 'Unknown error';
  
  process.stderr.write(`ERROR: ${message}: ${errorMessage}\n`);
} 
```

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

```typescript
/**
 * Widget Tools - Index file to export all widget-related tools
 */
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { getWidgetsTool } from "./get-widgets.js";
import { getWidgetTool } from "./get-widget.js";
import { getWidgetSyncDataTool } from "./get-widget-sync-data.js";
import { searchWidgetsTool } from "./search-widgets.js";
import { analyzeWidgetStructureTool } from "./analyze-widget-structure.js";

/**
 * Registers all widget-related tools with the MCP server
 * @param server The MCP server instance
 */
export function registerWidgetTools(server: McpServer): void {
  // Register all widget tools
  getWidgetsTool(server);
  getWidgetTool(server);
  getWidgetSyncDataTool(server);
  searchWidgetsTool(server);
  analyzeWidgetStructureTool(server);
}

```

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

```typescript
/**
 * Color utility functions for Figma plugin
 */

/**
 * Convert hex color to RGB object
 * @param hex Hexadecimal color string (with or without #)
 * @returns RGB object with values between 0 and 1
 */
export function hexToRgb(hex: string): RGB {
  hex = hex.replace('#', '');
  const r = parseInt(hex.substring(0, 2), 16) / 255;
  const g = parseInt(hex.substring(2, 4), 16) / 255;
  const b = parseInt(hex.substring(4, 6), 16) / 255;
  return { r, g, b };
}

/**
 * Convert RGB object to hex color string
 * @param rgb RGB object with values between 0 and 1
 * @returns Hexadecimal color string with #
 */
export function rgbToHex(rgb: RGB): string {
  const r = Math.round(rgb.r * 255).toString(16).padStart(2, '0');
  const g = Math.round(rgb.g * 255).toString(16).padStart(2, '0');
  const b = Math.round(rgb.b * 255).toString(16).padStart(2, '0');
  return `#${r}${g}${b}`;
}

/**
 * Create a solid color paint
 * @param color RGB color object or hex string
 * @returns Solid paint object
 */
export function createSolidPaint(color: RGB | string): SolidPaint {
  if (typeof color === 'string') {
    return { type: 'SOLID', color: hexToRgb(color) };
  }
  return { type: 'SOLID', color };
} 
```

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

```typescript
/**
 * Tools - Main index file for all MCP tools
 */
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { registerWidgetTools } from "./widget/index.js";
import { registerFileTools } from "./file.js";
import { registerNodeTools } from "./node.js";
import { registerCommentTools } from "./comment.js";
import { registerImageTools } from "./image.js";
import { registerVersionTools } from "./version.js";
import { registerSearchTools } from "./search.js";
import { registerComponentTools } from "./component.js";
import { registerFrameTools } from "./frame.js";
import { registerCanvasTools } from "./canvas.js";
import { registerPageTools } from "./page.js";

/**
 * Registers all tools with the MCP server
 * @param server The MCP server instance
 */
export function registerAllTools(server: McpServer): void {
  // Register all tool categories
  registerFileTools(server);
  registerNodeTools(server);
  registerCommentTools(server);
  registerImageTools(server);
  registerVersionTools(server);
  registerSearchTools(server);
  registerComponentTools(server);
  registerWidgetTools(server);
  registerFrameTools(server);
  registerCanvasTools(server);
  registerPageTools(server);
}

```

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

```json
{
  "name": "figma-mcp-server",
  "version": "1.0.0",
  "description": "MCP server for accessing Figma API with widget support",
  "type": "module",
  "main": "dist/index.js",
  "scripts": {
    "start": "bun run dist/index.js",
    "dev": "bun --watch src/index.ts",
    "mcp": "bun --watch src/index.ts",
    "build": "bun build src/index.ts --outdir dist --target node",
    "build:mcp": "bun build src/index.ts --outdir dist --target node",
    "build:widget": "bun build src/widget/widget.tsx --outfile dist/widget-code.js --target browser",
    "dev:widget": "bun build src/widget/widget.tsx --outfile dist/widget-code.js --target browser --watch",
    "build:plugin": "bun build src/plugin/code.ts --outfile src/plugin/code.js --target browser",
    "dev:plugin": "bun build src/plugin/code.ts --outfile src/plugin/code.js --target browser --watch",
    "test": "bun test"
  },
  "keywords": [
    "figma",
    "api",
    "mcp",
    "server",
    "widget"
  ],
  "author": "",
  "license": "MIT",
  "dependencies": {
    "@create-figma-plugin/ui": "^4.0.0",
    "@create-figma-plugin/utilities": "^4.0.0",
    "@figma/rest-api-spec": "^0.27.0",
    "@figma/widget-typings": "^1.11.0",
    "@modelcontextprotocol/sdk": "^1.9.0",
    "@types/ws": "^8.18.1",
    "axios": "^1.6.2",
    "cors": "^2.8.5",
    "dotenv": "^16.3.1",
    "express": "^5.1.0",
    "js-yaml": "^4.1.0",
    "ws": "^8.18.1",
    "zod": "^3.24.2"
  },
  "devDependencies": {
    "@figma/plugin-typings": "^1.109.0",
    "@types/cors": "^2.8.17",
    "@types/express": "^4.17.21",
    "@types/js-yaml": "^4.0.9",
    "@types/node": "^20.10.0",
    "typescript": "^5.3.2"
  }
}
```

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

```typescript
/**
 * Node tools for the Figma MCP server
 */
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import figmaApi from "../services/figma-api.js";

export const getNodeTool = (server: McpServer) => {
  server.tool(
    "get_node",
    {
      file_key: z.string().min(1).describe("The Figma file key to retrieve from"),
      node_id: z.string().min(1).describe("The ID of the node to retrieve")
    },
    async ({ file_key, node_id }) => {
      try {
        const fileNodes = await figmaApi.getFileNodes(file_key, [node_id]);
        const nodeData = fileNodes.nodes[node_id];
        
        if (!nodeData) {
          return {
            content: [
              { type: "text", text: `Node ${node_id} not found in file ${file_key}` }
            ]
          };
        }
        
        return {
          content: [
            { type: "text", text: `# Node: ${nodeData.document.name}` },
            { type: "text", text: `Type: ${nodeData.document.type}` },
            { type: "text", text: `ID: ${nodeData.document.id}` },
            { type: "text", text: `Children: ${nodeData.document.children?.length || 0}` },
            { type: "text", text: "```json\n" + JSON.stringify(nodeData.document, null, 2) + "\n```" }
          ]
        };
      } catch (error) {
        console.error('Error fetching node:', error);
        return {
          content: [
            { type: "text", text: `Error getting node: ${(error as Error).message}` }
          ]
        };
      }
    }
  );
};

/**
 * Registers all node-related tools with the MCP server
 */
export const registerNodeTools = (server: McpServer): void => {
  getNodeTool(server);
};

```

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

```markdown
# Basic Command Line Coding Assistant

You are a command line coding assistant. Help me write and manage code using these essential terminal commands:

## Basic File Operations

- View files recursively: `tree -fiI ".venv|node_modules|.git|dist|<MORE_IGNORE_PATTERNS>"`
- View file contents: `cat file.py`
- Search in files: `grep "function" file.py`
- Search recursively: `grep -r "pattern" directory/`
- Find files by name: `find . -name "*.py"`
- Write to file using cat:

  ```
  cat > file.py << EOF
  # Add your code here
  EOF
  ```

- Move/rename files: `mv oldname.py newname.py`

## Assistant Behavior

- Directly modify files without outputting code blocks
- Read/Write all of docs in the project directory ./docs
- Ensure code is not redundant or duplicative
- Prioritize implementation logic and ask user when facing decisions
- Maintain existing code style and naming conventions when modifying files
- Use concise commands to execute operations efficiently
- Consider performance implications when suggesting solutions
- Provide clear explanation of steps taken during complex operations
- Verify commands before execution, especially for destructive operations
- Suggest file organization improvements when appropriate
- Always write code in English, including all code, comments, and strings
- After fully understanding responsibilities, respond with "Ready to start coding now"

## Project Preferences

- TypeScript/Node.js: Use Bun instead of npm/node

  - Initialize: `bun init`
  - Install packages: `bun install <package>`
  - Run scripts: `bun run <script>`

- Default Project Files:
  - Create Makefile
  - Create .envrc:
- Project dir: /Users/ann/Workspace/MCP/figma
- Project language: TypeScript

```

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

```typescript
/**
 * Version tools for the Figma MCP server
 */
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import figmaApi from "../services/figma-api.js";

export const getFileVersionsTool = (server: McpServer) => {
  server.tool(
    "get_file_versions",
    {
      file_key: z.string().min(1).describe("The Figma file key")
    },
    async ({ file_key }) => {
      try {
        const versionsResponse = await figmaApi.getFileVersions(file_key);
        
        if (!versionsResponse.versions || versionsResponse.versions.length === 0) {
          return {
            content: [
              { type: "text", text: `No versions found for file ${file_key}` }
            ]
          };
        }
        
        const versionsList = versionsResponse.versions.map((version, index) => {
          return `${index + 1}. **${version.label || 'Unnamed version'}** - ${new Date(version.created_at).toLocaleString()} by ${version.user.handle}\n   ${version.description || 'No description'}`;
        }).join('\n\n');
        
        return {
          content: [
            { type: "text", text: `# File Versions for ${file_key}` },
            { type: "text", text: `Found ${versionsResponse.versions.length} versions:` },
            { type: "text", text: versionsList }
          ]
        };
      } catch (error) {
        console.error('Error fetching file versions:', error);
        return {
          content: [
            { type: "text", text: `Error getting file versions: ${(error as Error).message}` }
          ]
        };
      }
    }
  );
};

/**
 * Registers all version-related tools with the MCP server
 */
export const registerVersionTools = (server: McpServer): void => {
  getFileVersionsTool(server);
};

```

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

```typescript
/**
 * Component tools for the Figma MCP server
 */
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import figmaApi from "../services/figma-api.js";

export const getComponentsTool = (server: McpServer) => {
  server.tool(
    "get_components",
    {
      file_key: z.string().min(1).describe("The Figma file key")
    },
    async ({ file_key }) => {
      try {
        const componentsResponse = await figmaApi.getFileComponents(file_key);
        
        if (!componentsResponse.meta?.components || componentsResponse.meta.components.length === 0) {
          return {
            content: [
              { type: "text", text: `No components found in file ${file_key}` }
            ]
          };
        }
        
        const componentsList = componentsResponse.meta.components.map(component => {
          return `- **${component.name}** (Key: ${component.key})\n  Description: ${component.description || 'No description'}\n  ${component.remote ? '(Remote component)' : '(Local component)'}`;
        }).join('\n\n');
        
        return {
          content: [
            { type: "text", text: `# Components in file ${file_key}` },
            { type: "text", text: `Found ${componentsResponse.meta.components.length} components:` },
            { type: "text", text: componentsList }
          ]
        };
      } catch (error) {
        console.error('Error fetching components:', error);
        return {
          content: [
            { type: "text", text: `Error getting components: ${(error as Error).message}` }
          ]
        };
      }
    }
  );
};

/**
 * Registers all component-related tools with the MCP server
 */
export const registerComponentTools = (server: McpServer): void => {
  getComponentsTool(server);
};

```

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

```typescript
/**
 * Tool: get_widgets
 * 
 * Retrieves all widget nodes from a Figma file
 */
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import figmaApi from "../../services/figma-api.js";
import { FigmaUtils } from "../../utils/figma-utils.js";

export const getWidgetsTool = (server: McpServer) => {
  server.tool(
    "get_widgets",
    {
      file_key: z.string().min(1).describe("The Figma file key to retrieve widgets from")
    },
    async ({ file_key }) => {
      try {
        const file = await figmaApi.getFile(file_key);
        
        // Find all widget nodes in the file
        const widgetNodes = FigmaUtils.getNodesByType(file, 'WIDGET');
        
        if (widgetNodes.length === 0) {
          return {
            content: [
              { type: "text", text: `No widgets found in file ${file_key}` }
            ]
          };
        }
        
        const widgetsList = widgetNodes.map((node, index) => {
          const widgetSyncData = node.widgetSync ? 
            `\n   - Widget Sync Data: Available` : 
            `\n   - Widget Sync Data: None`;
            
          return `${index + 1}. **${node.name}** (ID: ${node.id})
   - Widget ID: ${node.widgetId || 'Unknown'}${widgetSyncData}`;
        }).join('\n\n');
        
        return {
          content: [
            { type: "text", text: `# Widgets in file ${file_key}` },
            { type: "text", text: `Found ${widgetNodes.length} widgets:` },
            { type: "text", text: widgetsList }
          ]
        };
      } catch (error) {
        console.error('Error fetching widgets:', error);
        return {
          content: [
            { type: "text", text: `Error getting widgets: ${(error as Error).message}` }
          ]
        };
      }
    }
  );
};

```

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

```typescript
/**
 * File tools for the Figma MCP server
 */
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import figmaApi from "../services/figma-api.js";

export const getFileTool = (server: McpServer) => {
  server.tool(
    "get_file",
    {
      file_key: z.string().min(1).describe("The Figma file key to retrieve"),
      return_full_file: z.boolean().default(false).describe("Whether to return the full file contents or just a summary")
    },
    async ({ file_key, return_full_file }) => {
      try {
        const file = await figmaApi.getFile(file_key);
        
        if (return_full_file) {
          return {
            content: [
              { type: "text", text: `Retrieved Figma file: ${file.name}` },
              { type: "text", text: JSON.stringify(file, null, 2) }
            ]
          };
        } else {
          return {
            content: [
              { type: "text", text: `# Figma File: ${file.name}` },
              { type: "text", text: `Last modified: ${file.lastModified}` },
              { type: "text", text: `Document contains ${file.document.children?.length || 0} top-level nodes.` },
              { type: "text", text: `Components: ${Object.keys(file.components).length || 0}` },
              { type: "text", text: `Component sets: ${Object.keys(file.componentSets).length || 0}` },
              { type: "text", text: `Styles: ${Object.keys(file.styles).length || 0}` }
            ]
          };
        }
      } catch (error) {
        console.error('Error fetching file:', error);
        return {
          content: [
            { type: "text", text: `Error getting Figma file: ${(error as Error).message}` }
          ]
        };
      }
    }
  );
};

/**
 * Registers all file-related tools with the MCP server
 */
export const registerFileTools = (server: McpServer): void => {
  getFileTool(server);
};

```

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

```typescript
/**
 * Image tools for the Figma MCP server
 */
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import figmaApi from "../services/figma-api.js";

export const getImagesTool = (server: McpServer) => {
  server.tool(
    "get_images",
    {
      file_key: z.string().min(1).describe("The Figma file key"),
      node_ids: z.array(z.string()).min(1).describe("The IDs of nodes to export as images"),
      format: z.enum(["jpg", "png", "svg", "pdf"]).default("png").describe("Image format to export"),
      scale: z.number().min(0.01).max(4).default(1).describe("Scale factor for the image (0.01 to 4)")
    },
    async ({ file_key, node_ids, format, scale }) => {
      try {
        const imagesResponse = await figmaApi.getImages(file_key, node_ids, {
          format,
          scale
        });
        
        if (imagesResponse.err) {
          return {
            content: [
              { type: "text", text: `Error getting images: ${imagesResponse.err}` }
            ]
          };
        }
        
        const imageUrls = Object.entries(imagesResponse.images)
          .map(([nodeId, url]) => {
            if (!url) {
              return `- ${nodeId}: Error generating image`;
            }
            return `- ${nodeId}: [Image URL](${url})`;
          })
          .join('\n');
        
        return {
          content: [
            { type: "text", text: `# Images for file ${file_key}` },
            { type: "text", text: `Format: ${format}, Scale: ${scale}` },
            { type: "text", text: imageUrls }
          ]
        };
      } catch (error) {
        console.error('Error fetching images:', error);
        return {
          content: [
            { type: "text", text: `Error getting images: ${(error as Error).message}` }
          ]
        };
      }
    }
  );
};

/**
 * Registers all image-related tools with the MCP server
 */
export const registerImageTools = (server: McpServer): void => {
  getImagesTool(server);
};

```

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

```typescript
/**
 * Tool: get_widget
 * 
 * Retrieves detailed information about a specific widget node
 */
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import figmaApi from "../../services/figma-api.js";

export const getWidgetTool = (server: McpServer) => {
  server.tool(
    "get_widget",
    {
      file_key: z.string().min(1).describe("The Figma file key"),
      node_id: z.string().min(1).describe("The ID of the widget node")
    },
    async ({ file_key, node_id }) => {
      try {
        const fileNodes = await figmaApi.getFileNodes(file_key, [node_id]);
        const nodeData = fileNodes.nodes[node_id];
        
        if (!nodeData || nodeData.document.type !== 'WIDGET') {
          return {
            content: [
              { type: "text", text: `Node ${node_id} not found in file ${file_key} or is not a widget` }
            ]
          };
        }
        
        const widgetNode = nodeData.document;
        
        // Get the sync data if available
        let syncDataContent = '';
        if (widgetNode.widgetSync) {
          try {
            const syncData = JSON.parse(widgetNode.widgetSync);
            syncDataContent = `\n\n## Widget Sync Data\n\`\`\`json\n${JSON.stringify(syncData, null, 2)}\n\`\`\``;
          } catch (error) {
            syncDataContent = '\n\n## Widget Sync Data\nError parsing widget sync data';
          }
        }
        
        return {
          content: [
            { type: "text", text: `# Widget: ${widgetNode.name}` },
            { type: "text", text: `ID: ${widgetNode.id}` },
            { type: "text", text: `Widget ID: ${widgetNode.widgetId || 'Unknown'}` },
            { type: "text", text: `Has Sync Data: ${widgetNode.widgetSync ? 'Yes' : 'No'}${syncDataContent}` }
          ]
        };
      } catch (error) {
        console.error('Error fetching widget node:', error);
        return {
          content: [
            { type: "text", text: `Error getting widget: ${(error as Error).message}` }
          ]
        };
      }
    }
  );
};

```

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

```typescript
/**
 * Search tools for the Figma MCP server
 */
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import figmaApi from "../services/figma-api.js";
import { FigmaUtils } from "../utils/figma-utils.js";

export const searchTextTool = (server: McpServer) => {
  server.tool(
    "search_text",
    {
      file_key: z.string().min(1).describe("The Figma file key"),
      search_text: z.string().min(1).describe("The text to search for in the file")
    },
    async ({ file_key, search_text }) => {
      try {
        const file = await figmaApi.getFile(file_key);
        
        // Find all TEXT nodes
        const textNodes = FigmaUtils.getNodesByType(file, 'TEXT');
        
        // Filter for nodes containing the search text
        const matchingNodes = textNodes.filter(node => 
          node.characters && node.characters.toLowerCase().includes(search_text.toLowerCase())
        );
        
        if (matchingNodes.length === 0) {
          return {
            content: [
              { type: "text", text: `No text matching "${search_text}" found in file ${file_key}` }
            ]
          };
        }
        
        const matchesList = matchingNodes.map(node => {
          const path = FigmaUtils.getNodePath(file, node.id);
          return `- **${node.name}** (ID: ${node.id})\n  Path: ${path.join(' > ')}\n  Text: "${node.characters}"`;
        }).join('\n\n');
        
        return {
          content: [
            { type: "text", text: `# Text Search Results for "${search_text}"` },
            { type: "text", text: `Found ${matchingNodes.length} matching text nodes:` },
            { type: "text", text: matchesList }
          ]
        };
      } catch (error) {
        console.error('Error searching text:', error);
        return {
          content: [
            { type: "text", text: `Error searching text: ${(error as Error).message}` }
          ]
        };
      }
    }
  );
};

/**
 * Registers all search-related tools with the MCP server
 */
export const registerSearchTools = (server: McpServer): void => {
  searchTextTool(server);
};

```

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

```typescript
/**
 * Tool: get_widget_sync_data
 * 
 * Retrieves the synchronized state data for a specific widget
 */
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import figmaApi from "../../services/figma-api.js";

export const getWidgetSyncDataTool = (server: McpServer) => {
  server.tool(
    "get_widget_sync_data",
    {
      file_key: z.string().min(1).describe("The Figma file key"),
      node_id: z.string().min(1).describe("The ID of the widget node")
    },
    async ({ file_key, node_id }) => {
      try {
        const fileNodes = await figmaApi.getFileNodes(file_key, [node_id]);
        const nodeData = fileNodes.nodes[node_id];
        
        if (!nodeData || nodeData.document.type !== 'WIDGET') {
          return {
            content: [
              { type: "text", text: `Node ${node_id} not found in file ${file_key} or is not a widget` }
            ]
          };
        }
        
        const widgetNode = nodeData.document;
        
        if (!widgetNode.widgetSync) {
          return {
            content: [
              { type: "text", text: `Widget ${node_id} does not have any sync data` }
            ]
          };
        }
        
        try {
          const syncData = JSON.parse(widgetNode.widgetSync);
          
          return {
            content: [
              { type: "text", text: `# Widget Sync Data for "${widgetNode.name}"` },
              { type: "text", text: `Widget ID: ${widgetNode.id}` },
              { type: "text", text: "```json\n" + JSON.stringify(syncData, null, 2) + "\n```" }
            ]
          };
        } catch (error) {
          console.error('Error parsing widget sync data:', error);
          return {
            content: [
              { type: "text", text: `Error parsing widget sync data: ${(error as Error).message}` }
            ]
          };
        }
      } catch (error) {
        console.error('Error fetching widget sync data:', error);
        return {
          content: [
            { type: "text", text: `Error getting widget sync data: ${(error as Error).message}` }
          ]
        };
      }
    }
  );
};

```

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

```markdown
# Figma MCP Server - Project Overview

## Introduction

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.

## Purpose

This project aims to:

1. Provide AI assistants with the ability to access and manipulate Figma files
2. Enable structured access to Figma resources through the MCP protocol
3. Bridge the gap between design tools and AI systems
4. Support design workflows with AI-assisted operations

## Core Features

- **File Access**: Retrieve Figma files and inspect their contents
- **Node Operations**: Access specific nodes within Figma files
- **Comment Management**: Read and write comments on Figma files
- **Image Export**: Export nodes as images in various formats
- **Search Capabilities**: Search for text and elements within files
- **Component Access**: View and work with Figma components
- **Version History**: Access file version history

## Technology Stack

- **TypeScript**: Type-safe implementation
- **Bun**: JavaScript/TypeScript runtime and package manager
- **MCP SDK**: Model Context Protocol implementation
- **Figma REST API**: Official Figma API with TypeScript definitions
- **Zod**: Schema validation for parameters and configurations

## Project Structure

```
/figma
├── dist/                 # Compiled output
├── docs/                 # Documentation
├── src/
│   ├── config/           # Configuration files
│   ├── services/         # API and external service integrations
│   └── utils/            # Utility functions
├── types/                # Type definitions
├── .env                  # Environment variables
├── Makefile              # Build and run commands
├── README.md             # Project README
├── package.json          # Dependencies and scripts
└── tsconfig.json         # TypeScript configuration
```

## Integration with AI Systems

This MCP server enables AI assistants to:

1. Retrieve design information from Figma
2. Answer questions about design files
3. Generate images and assets from Figma files
4. Add comments and feedback to designs
5. Search for specific elements or text within designs
6. Track version history and changes

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.

```

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

```typescript
/**
 * Widget Utils - Helper functions for widget tools
 */
import type { Node } from '@figma/rest-api-spec';

/**
 * Parses the widget sync data from a widget node
 * @param node A Figma node of type WIDGET
 * @returns The parsed sync data object or null if not available/invalid
 */
export function parseWidgetSyncData(node: Node): Record<string, any> | null {
  if (node.type !== 'WIDGET' || !node.widgetSync) {
    return null;
  }
  
  try {
    return JSON.parse(node.widgetSync);
  } catch (error) {
    console.error('Error parsing widget sync data:', error);
    return null;
  }
}

/**
 * Formats widget sync data as a string
 * @param syncData The widget sync data object
 * @returns A formatted string representation of the sync data
 */
export function formatWidgetSyncData(syncData: Record<string, any> | null): string {
  if (!syncData) {
    return 'No sync data available';
  }
  
  return JSON.stringify(syncData, null, 2);
}

/**
 * Gets a summary of the widget's properties
 * @param node A Figma node of type WIDGET
 * @returns A summary object with key widget properties
 */
export function getWidgetSummary(node: Node): Record<string, any> {
  if (node.type !== 'WIDGET') {
    return { error: 'Not a widget node' };
  }
  
  const summary: Record<string, any> = {
    id: node.id,
    name: node.name,
    type: 'WIDGET',
    widgetId: node.widgetId || 'Unknown',
  };
  
  // If there's widget sync data, analyze it
  if (node.widgetSync) {
    try {
      const syncData = JSON.parse(node.widgetSync);
      const syncKeys = Object.keys(syncData);
      
      summary.syncDataKeys = syncKeys;
      summary.hasSyncData = syncKeys.length > 0;
    } catch (error) {
      summary.hasSyncData = false;
      summary.syncDataError = 'Invalid sync data format';
    }
  } else {
    summary.hasSyncData = false;
  }
  
  return summary;
}

/**
 * Creates a human-readable description of the widget
 * @param node A Figma node of type WIDGET
 * @returns A detailed text description of the widget
 */
export function createWidgetDescription(node: Node): string {
  if (node.type !== 'WIDGET') {
    return 'Not a widget node';
  }
  
  let description = `Widget "${node.name}" (ID: ${node.id})`;
  
  if (node.widgetId) {
    description += `\nWidget ID: ${node.widgetId}`;
  }
  
  if (node.widgetSync) {
    try {
      const syncData = JSON.parse(node.widgetSync);
      const syncKeys = Object.keys(syncData);
      
      description += `\nSync Data Keys: ${syncKeys.join(', ')}`;
    } catch (error) {
      description += '\nSync Data: [Invalid format]';
    }
  } else {
    description += '\nSync Data: None';
  }
  
  return description;
}

```

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

```typescript
/**
 * Utility functions for handling Figma nodes
 */

/**
 * Apply common properties to any node type
 * Safely handles properties that might not be available on all node types
 * 
 * @param node The target node to apply properties to
 * @param data Object containing the properties to apply
 */
export function applyCommonProperties(node: SceneNode, data: any): void {
  // Position
  if (data.x !== undefined) node.x = data.x;
  if (data.y !== undefined) node.y = data.y;
  
  // Name
  if (data.name) node.name = data.name;
  
  // Properties that aren't available on all node types
  // We need to check if they exist before setting them
  
  // Opacity
  if (data.opacity !== undefined && 'opacity' in node) {
    (node as any).opacity = data.opacity;
  }
  
  // Blend mode
  if (data.blendMode && 'blendMode' in node) {
    (node as any).blendMode = data.blendMode;
  }
  
  // Effects
  if (data.effects && 'effects' in node) {
    (node as any).effects = data.effects;
  }
  
  // Constraint
  if (data.constraints && 'constraints' in node) {
    (node as any).constraints = data.constraints;
  }
  
  // Is Mask
  if (data.isMask !== undefined && 'isMask' in node) {
    (node as any).isMask = data.isMask;
  }
  
  // Visible
  if (data.visible !== undefined) node.visible = data.visible;
  
  // Locked
  if (data.locked !== undefined) node.locked = data.locked;
}

/**
 * Select and focus on a node or set of nodes
 * @param nodes Node or array of nodes to focus on
 */
export function selectAndFocusNodes(nodes: SceneNode | SceneNode[]): void {
  const nodesToFocus = Array.isArray(nodes) ? nodes : [nodes];
  figma.currentPage.selection = nodesToFocus;
  figma.viewport.scrollAndZoomIntoView(nodesToFocus);
}

/**
 * Build a result object from a node or array of nodes
 * @param result Node or array of nodes to create a result object from
 * @returns Object containing node information in a consistent format
 */
export function buildResultObject(result: SceneNode | readonly SceneNode[] | null): {[key: string]: any} {
  let resultObject: {[key: string]: any} = {};
  
  if (!result) return resultObject;
  
  if (Array.isArray(result)) {
    // Handle array result (like from get-selection)
    resultObject.count = result.length;
    if (result.length > 0) {
      resultObject.items = result.map(node => ({
        id: node.id,
        type: node.type,
        name: node.name
      }));
    }
  } else {
    // Handle single node result - we know it's a SceneNode at this point
    const node = result as SceneNode;
    resultObject.id = node.id;
    resultObject.type = node.type;
    resultObject.name = node.name;
  }
  
  return resultObject;
} 
```

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

```typescript
/**
 * Tool: analyze_widget_structure
 * 
 * Provides a detailed analysis of a widget's structure and properties
 */
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import figmaApi from "../../services/figma-api.js";

export const analyzeWidgetStructureTool = (server: McpServer) => {
  server.tool(
    "analyze_widget_structure",
    {
      file_key: z.string().min(1).describe("The Figma file key"),
      node_id: z.string().min(1).describe("The ID of the widget node")
    },
    async ({ file_key, node_id }) => {
      try {
        const fileNodes = await figmaApi.getFileNodes(file_key, [node_id]);
        const nodeData = fileNodes.nodes[node_id];
        
        if (!nodeData || nodeData.document.type !== 'WIDGET') {
          return {
            content: [
              { type: "text", text: `Node ${node_id} not found in file ${file_key} or is not a widget` }
            ]
          };
        }
        
        const widgetNode = nodeData.document;
        
        // Create a full analysis of the widget
        const widgetAnalysis = {
          basic: {
            id: widgetNode.id,
            name: widgetNode.name,
            type: widgetNode.type,
            widgetId: widgetNode.widgetId || 'Unknown'
          },
          placement: {
            x: widgetNode.x || 0,
            y: widgetNode.y || 0,
            width: widgetNode.width || 0,
            height: widgetNode.height || 0,
            rotation: widgetNode.rotation || 0
          },
          syncData: null as any
        };
        
        // Parse the widget sync data if available
        if (widgetNode.widgetSync) {
          try {
            widgetAnalysis.syncData = JSON.parse(widgetNode.widgetSync);
          } catch (error) {
            widgetAnalysis.syncData = { error: 'Invalid sync data format' };
          }
        }
        
        return {
          content: [
            { type: "text", text: `# Widget Analysis: ${widgetNode.name}` },
            { type: "text", text: `## Basic Information` },
            { type: "text", text: "```json\n" + JSON.stringify(widgetAnalysis.basic, null, 2) + "\n```" },
            { type: "text", text: `## Placement` },
            { type: "text", text: "```json\n" + JSON.stringify(widgetAnalysis.placement, null, 2) + "\n```" },
            { type: "text", text: `## Sync Data` },
            { type: "text", text: widgetAnalysis.syncData ? 
              "```json\n" + JSON.stringify(widgetAnalysis.syncData, null, 2) + "\n```" : 
              "No sync data available" 
            }
          ]
        };
      } catch (error) {
        console.error('Error analyzing widget:', error);
        return {
          content: [
            { type: "text", text: `Error analyzing widget: ${(error as Error).message}` }
          ]
        };
      }
    }
  );
};

```

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

```typescript
/**
 * WebSocket Service - Handles communication with Figma plugin
 */
import { WebSocketServer, WebSocket as WSWebSocket } from "ws";
import { log, logError } from "../utils.js";

// Store active plugin connection WebSocket
let activePluginConnection: WSWebSocket | null = null;

// Callbacks for handling responses
const pendingCommands = new Map<string, (response: any) => void>();

interface PluginResponse {
  success: boolean;
  result?: any;
  error?: string;
}

/**
 * Create WebSocket server
 */
export function initializeWebSocketServer(port = 3001) {
  const wss = new WebSocketServer({ port });
  log(`WebSocket server started on port ${port}`);

  wss.on("connection", (ws: WSWebSocket) => {
    log("New WebSocket connection");

    ws.on("message", (message: WSWebSocket.Data) => {
      try {
        const data = JSON.parse(message.toString());
        log(`Received WebSocket message: ${JSON.stringify(data)}`);

        if (data.type === "figma-plugin-connected") {
          // Store active connection
          activePluginConnection = ws;
          log(`Figma plugin connected: ${data.pluginId || "unknown"}`);
        } else if (data.type === "figma-plugin-response") {
          // Handle response from plugin
          const { command, success, result, error } = data;
          const callback = pendingCommands.get(command);

          if (callback) {
            callback({ success, result, error });
            pendingCommands.delete(command);
          }
        }
      } catch (error) {
        logError("Error processing WebSocket message", error);
      }
    });

    ws.on("close", () => {
      log("WebSocket connection closed");
      if (activePluginConnection === ws) {
        activePluginConnection = null;
      }
    });

    ws.on("error", (error: Error) => {
      logError("WebSocket error", error);
    });
  });

  return wss;
}

/**
 * Send command to Figma plugin
 */
export async function sendCommandToPlugin(
  command: string,
  params: any
): Promise<PluginResponse> {
  return new Promise((resolve, reject) => {
    if (!activePluginConnection) {
      reject(new Error("No active Figma plugin connection"));
      return;
    }

    try {
      // Store callback
      pendingCommands.set(command, resolve);

      // Send command
      activePluginConnection.send(
        JSON.stringify({
          type: "mcp-command",
          command,
          params,
        })
      );

      // Set timeout
      setTimeout(() => {
        if (pendingCommands.has(command)) {
          pendingCommands.delete(command);
          reject(new Error(`Command ${command} timed out`));
        }
      }, 10000); // 10 second timeout
    } catch (error) {
      pendingCommands.delete(command);
      reject(error);
    }
  });
}

/**
 * Check if a Figma plugin is connected
 */
export function isPluginConnected(): boolean {
  return activePluginConnection !== null;
} 
```

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

```typescript
/**
 * Utility functions for working with Figma Widgets
 */
import type { GetFileResponse, Node } from '@figma/rest-api-spec';
import type { WidgetNode, WidgetSyncData } from '../services/widget-api.js';

/**
 * Utility functions for working with Figma Widgets
 */
export class WidgetUtils {
  /**
   * Find all widget nodes in a file
   */
  static findAllWidgetNodes(file: GetFileResponse): Node[] {
    const widgetNodes: Node[] = [];
    
    // Helper function to recursively search for widget nodes
    const findWidgets = (node: Node) => {
      if (node.type === 'WIDGET') {
        widgetNodes.push(node);
      }
      
      if (node.children) {
        for (const child of node.children) {
          findWidgets(child);
        }
      }
    };
    
    findWidgets(file.document);
    return widgetNodes;
  }
  
  /**
   * Extract widget sync data from a widget node
   */
  static extractWidgetSyncData(node: Node): WidgetSyncData | null {
    if (node.type !== 'WIDGET' || !node.widgetSync) {
      return null;
    }
    
    try {
      return JSON.parse(node.widgetSync);
    } catch (error) {
      console.error('Error parsing widget sync data:', error);
      return null;
    }
  }
  
  /**
   * Format widget sync data for display
   */
  static formatWidgetSyncData(syncData: WidgetSyncData | null): string {
    if (!syncData) {
      return 'No sync data available';
    }
    
    return JSON.stringify(syncData, null, 2);
  }
  
  /**
   * Get a summary of a widget node
   */
  static getWidgetSummary(node: WidgetNode): Record<string, any> {
    const { id, name, widgetId } = node;
    
    const summary: Record<string, any> = {
      id,
      name,
      type: 'WIDGET',
      widgetId: widgetId || 'Unknown',
    };
    
    // If there's widget sync data, add a summary
    if (node.widgetSync) {
      try {
        const syncData = JSON.parse(node.widgetSync);
        const syncKeys = Object.keys(syncData);
        
        summary.syncDataKeys = syncKeys;
        summary.hasSyncData = syncKeys.length > 0;
      } catch (error) {
        summary.hasSyncData = false;
        summary.syncDataError = 'Invalid sync data format';
      }
    } else {
      summary.hasSyncData = false;
    }
    
    return summary;
  }
  
  /**
   * Check if a node is a widget
   */
  static isWidgetNode(node: Node): boolean {
    return node.type === 'WIDGET';
  }
  
  /**
   * Create a human-readable description of a widget
   */
  static createWidgetDescription(widget: WidgetNode): string {
    let description = ;
    
    if (widget.widgetId) {
      description += ;
    }
    
    if (widget.widgetSync) {
      try {
        const syncData = JSON.parse(widget.widgetSync);
        const syncKeys = Object.keys(syncData);
        
        description += ;
      } catch (error) {
        description += '\nSync Data: [Invalid format]';
      }
    } else {
      description += '\nSync Data: None';
    }
    
    return description;
  }
}

```

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

```typescript
/**
 * Component-related creation functions for Figma plugin
 */

import { applyCommonProperties } from '../utils/nodeUtils';

/**
 * Create a component from another node
 * @param data Configuration data with sourceNode reference
 * @returns Created component node
 */
export function createComponentFromNodeData(data: any): ComponentNode | null {
  // We need a sourceNode to create a component from
  if (!data.sourceNode) {
    console.error('createComponentFromNode requires a sourceNode');
    return null;
  }
  
  try {
    // If sourceNode is a string, try to find it by ID
    let sourceNode;
    if (typeof data.sourceNode === 'string') {
      sourceNode = figma.getNodeById(data.sourceNode);
      if (!sourceNode || !('type' in sourceNode)) {
        console.error(`Node with ID ${data.sourceNode} not found or is not a valid node`);
        return null;
      }
    } else {
      sourceNode = data.sourceNode;
    }
    
    // Create the component from the source node
    const component = figma.createComponentFromNode(sourceNode as SceneNode);
    
    // Apply component-specific properties
    if (data.description) component.description = data.description;
    
    // Apply common properties
    applyCommonProperties(component, data);
    
    return component;
  } catch (error) {
    console.error('Failed to create component from node:', error);
    return null;
  }
}

/**
 * Create a component set (variant container)
 * @param data Configuration data for component set
 * @returns Created component set node
 */
export function createComponentSetFromData(data: any): ComponentSetNode | null {
  try {
    // Create an empty component set
    // In practice, component sets are usually created by combining variants
    // using figma.combineAsVariants, not directly created
    
    // Get the components to combine
    if (!data.components || !Array.isArray(data.components) || data.components.length === 0) {
      console.error('Component set creation requires component nodes');
      return null;
    }
    
    const componentNodes: ComponentNode[] = [];
    
    // Collect the component nodes (could be IDs or actual nodes)
    for (const component of data.components) {
      let node;
      
      if (typeof component === 'string') {
        // If it's a string, assume it's a node ID
        node = figma.getNodeById(component);
      } else {
        node = component;
      }
      
      if (node && node.type === 'COMPONENT') {
        componentNodes.push(node as ComponentNode);
      }
    }
    
    if (componentNodes.length === 0) {
      console.error('No valid component nodes provided');
      return null;
    }
    
    // Combine the components as variants
    const componentSet = figma.combineAsVariants(componentNodes, figma.currentPage);
    
    // Apply component set properties
    if (data.name) componentSet.name = data.name;
    
    // Apply common properties
    applyCommonProperties(componentSet, data);
    
    return componentSet;
  } catch (error) {
    console.error('Failed to create component set:', error);
    return null;
  }
} 
```

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

```typescript
import type { GetFileResponse, Node } from '@figma/rest-api-spec';

/**
 * Utility functions for working with Figma files and nodes
 */
export class FigmaUtils {
  /**
   * Find a node by ID in a Figma file
   */
  static findNodeById(file: GetFileResponse, nodeId: string): Node | null {
    // If node ID is the document itself
    if (nodeId === file.document.id) {
      return file.document;
    }

    // Helper function to recursively search for node
    const findNode = (node: Node): Node | null => {
      if (node.id === nodeId) {
        return node;
      }

      if (node.children) {
        for (const child of node.children) {
          const found = findNode(child);
          if (found) return found;
        }
      }

      return null;
    };

    return findNode(file.document);
  }

  /**
   * Get all nodes of a specific type from a Figma file
   */
  static getNodesByType(file: GetFileResponse, type: string): Node[] {
    const nodes: Node[] = [];

    // Helper function to recursively search for nodes
    const findNodes = (node: Node) => {
      if (node.type === type) {
        nodes.push(node);
      }

      if (node.children) {
        for (const child of node.children) {
          findNodes(child);
        }
      }
    };

    findNodes(file.document);
    return nodes;
  }

  /**
   * Format a node ID for display (e.g., "1:2" -> "Node 1:2")
   */
  static formatNodeId(nodeId: string): string {
    return `Node ${nodeId}`;
  }

  /**
   * Extract text content from a TEXT node
   */
  static extractTextFromNode(node: Node): string {
    return node.type === 'TEXT' ? node.characters || '' : '';
  }

  /**
   * Get a simple representation of a node's properties
   */
  static getNodeProperties(node: Node): Record<string, any> {
    const { id, name, type } = node;
    let properties: Record<string, any> = { id, name, type };

    // Add type-specific properties
    switch (node.type) {
      case 'TEXT':
        properties.text = node.characters;
        break;
      case 'RECTANGLE':
      case 'ELLIPSE':
      case 'POLYGON':
      case 'STAR':
      case 'VECTOR':
        if (node.fills) {
          properties.fills = node.fills.map(fill => ({
            type: fill.type,
            visible: fill.visible,
          }));
        }
        break;
      case 'FRAME':
      case 'GROUP':
      case 'INSTANCE':
      case 'COMPONENT':
        properties.childCount = node.children?.length || 0;
        break;
    }

    return properties;
  }

  /**
   * Get the path to a node in the document
   */
  static getNodePath(file: GetFileResponse, nodeId: string): string[] {
    const path: string[] = [];
    
    const findPath = (node: Node, target: string): boolean => {
      if (node.id === target) {
        path.unshift(node.name);
        return true;
      }
      
      if (node.children) {
        for (const child of node.children) {
          if (findPath(child, target)) {
            path.unshift(node.name);
            return true;
          }
        }
      }
      
      return false;
    };
    
    findPath(file.document, nodeId);
    return path;
  }
}

```

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

```markdown
# Figma Widget Tools Guide

This guide explains how to use the MCP server's Widget Tools for interacting with Figma widgets.

## Overview

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.

## Available Widget Tools

### 1. get_widgets

Retrieves all widget nodes from a Figma file.

**Parameters:**
- `file_key` (string): The Figma file key to retrieve widgets from

**Example:**
```json
{
  "file_key": "abcxyz123456"
}
```

**Response:**
Returns a list of all widgets in the file, including their names, IDs, and whether they have sync data.

### 2. get_widget

Retrieves detailed information about a specific widget node.

**Parameters:**
- `file_key` (string): The Figma file key
- `node_id` (string): The ID of the widget node

**Example:**
```json
{
  "file_key": "abcxyz123456",
  "node_id": "1:123"
}
```

**Response:**
Returns detailed information about the specified widget, including its sync data if available.

### 3. get_widget_sync_data

Retrieves the synchronized state data for a specific widget.

**Parameters:**
- `file_key` (string): The Figma file key
- `node_id` (string): The ID of the widget node

**Example:**
```json
{
  "file_key": "abcxyz123456",
  "node_id": "1:123"
}
```

**Response:**
Returns the raw sync data (state) for the specified widget in JSON format.

### 4. search_widgets

Searches for widgets that have specific sync data properties and values.

**Parameters:**
- `file_key` (string): The Figma file key
- `property_key` (string): The sync data property key to search for
- `property_value` (string, optional): Optional property value to match

**Example:**
```json
{
  "file_key": "abcxyz123456",
  "property_key": "count",
  "property_value": "5"
}
```

**Response:**
Returns a list of widgets that match the search criteria.

### 5. analyze_widget_structure

Provides a detailed analysis of a widget's structure and properties.

**Parameters:**
- `file_key` (string): The Figma file key
- `node_id` (string): The ID of the widget node

**Example:**
```json
{
  "file_key": "abcxyz123456",
  "node_id": "1:123"
}
```

**Response:**
Returns a comprehensive analysis of the widget's structure, including basic information, placement details, and sync data.

## Widget Integration

These tools can be used to:

1. Discover widgets in Figma files
2. Analyze widget properties and state
3. Search for widgets with specific characteristics
4. Extract widget sync data for external processing
5. Generate reports about widget usage in design files

## Implementation Details

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.

## Future Enhancements

Planned enhancements for widget tools include:

- Widget state modification capabilities (requires special access)
- Widget creation and deletion
- Widget template libraries
- Widget analytics and usage statistics

```

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

```typescript
/**
 * Comment tools for the Figma MCP server
 */
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import figmaApi from "../services/figma-api.js";

export const getCommentsTool = (server: McpServer) => {
  server.tool(
    "get_comments",
    {
      file_key: z.string().min(1).describe("The Figma file key to retrieve comments from")
    },
    async ({ file_key }) => {
      try {
        const commentsResponse = await figmaApi.getComments(file_key, { as_md: true });
        
        if (!commentsResponse.comments || commentsResponse.comments.length === 0) {
          return {
            content: [
              { type: "text", text: `No comments found in file ${file_key}` }
            ]
          };
        }
        
        const commentsList = commentsResponse.comments.map(comment => {
          return `- **${comment.user.handle}** (${new Date(comment.created_at).toLocaleString()}): ${comment.message}`;
        }).join('\n');
        
        return {
          content: [
            { type: "text", text: `# Comments for file ${file_key}` },
            { type: "text", text: `Found ${commentsResponse.comments.length} comments:` },
            { type: "text", text: commentsList }
          ]
        };
      } catch (error) {
        console.error('Error fetching comments:', error);
        return {
          content: [
            { type: "text", text: `Error getting comments: ${(error as Error).message}` }
          ]
        };
      }
    }
  );
};

export const addCommentTool = (server: McpServer) => {
  server.tool(
    "add_comment",
    {
      file_key: z.string().min(1).describe("The Figma file key"),
      message: z.string().min(1).describe("The comment text"),
      node_id: z.string().optional().describe("Optional node ID to attach the comment to")
    },
    async ({ file_key, message, node_id }) => {
      try {
        const commentData: any = { message };
        
        // If node_id is provided, create a client_meta object
        if (node_id) {
          // Create a frame offset client_meta
          commentData.client_meta = {
            node_id: node_id,
            node_offset: {
              x: 0,
              y: 0
            }
          };
        }
        
        const commentResponse = await figmaApi.postComment(file_key, commentData);
        
        return {
          content: [
            { type: "text", text: `Comment added successfully!` },
            { type: "text", text: `Comment ID: ${commentResponse.id}` },
            { type: "text", text: `By user: ${commentResponse.user.handle}` },
            { type: "text", text: `Added at: ${new Date(commentResponse.created_at).toLocaleString()}` }
          ]
        };
      } catch (error) {
        console.error('Error adding comment:', error);
        return {
          content: [
            { type: "text", text: `Error adding comment: ${(error as Error).message}` }
          ]
        };
      }
    }
  );
};

/**
 * Registers all comment-related tools with the MCP server
 */
export const registerCommentTools = (server: McpServer): void => {
  getCommentsTool(server);
  addCommentTool(server);
};

```

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

```typescript
/**
 * Figma MCP Server - Main entry point
 *
 * This server provides a Model Context Protocol (MCP) implementation
 * for interacting with the Figma API.
 */
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { execSync } from "child_process";
import * as dotenv from "dotenv";
import { env } from "./config/env.js";
import { registerAllResources } from "./resources.js";
import { initializeWebSocketServer } from "./services/websocket.js";
import { registerAllTools } from "./tools/index.js";
import { log } from "./utils.js";

// Load environment variables
dotenv.config();

// Check for and kill any existing processes using the same port
function killExistingProcesses() {
  try {
    const wsPort = env.WEBSOCKET_PORT || 3001;
    log(`Checking for processes using port ${wsPort}...`);

    // Find processes using the websocket port
    const findCmd =
      process.platform === "win32"
        ? `netstat -ano | findstr :${wsPort}`
        : `lsof -i:${wsPort} | grep LISTEN`;

    let output;
    try {
      output = execSync(findCmd, { encoding: "utf8" });
    } catch (e) {
      // No process found, which is fine
      log("No existing processes found.");
      return;
    }

    // Extract PIDs and kill them
    if (output) {
      if (process.platform === "win32") {
        // Windows: extract PID from last column
        const pids = output
          .split("\n")
          .filter((line) => line.trim())
          .map((line) => line.trim().split(/\s+/).pop())
          .filter((pid, index, self) => pid && self.indexOf(pid) === index);

        pids.forEach((pid) => {
          if (pid && parseInt(pid) !== process.pid) {
            try {
              execSync(`taskkill /F /PID ${pid}`);
              log(`Killed process with PID: ${pid}`);
            } catch (e) {
              log(`Failed to kill process with PID: ${pid}`);
            }
          }
        });
      } else {
        // Unix-like: extract PID from second column
        const pids = output
          .split("\n")
          .filter((line) => line.trim())
          .map((line) => {
            const parts = line.trim().split(/\s+/);
            return parts[1];
          })
          .filter((pid, index, self) => pid && self.indexOf(pid) === index);

        pids.forEach((pid) => {
          if (pid && parseInt(pid) !== process.pid) {
            try {
              execSync(`kill -9 ${pid}`);
              log(`Killed process with PID: ${pid}`);
            } catch (e) {
              log(`Failed to kill process with PID: ${pid}`);
            }
          }
        });
      }
    }
  } catch (error) {
    log(`Error checking for existing processes: ${error}`);
  }
}

// Kill any existing processes before starting
killExistingProcesses();

// Create an MCP server
const server = new McpServer({
  name: "Figma API",
  version: "1.0.0",
});

// Register all tools and resources
registerAllTools(server);
registerAllResources(server);

// Initialize WebSocket server for Figma plugin communication
const wsPort = env.WEBSOCKET_PORT || 3001;
initializeWebSocketServer(wsPort);

// Start the MCP server with stdio transport
const transport = new StdioServerTransport();
server.connect(transport);

// Use logger utility to avoid interfering with stdout used by MCP
log("Figma MCP Server started");

export { server };

```

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

```typescript
/**
 * Slice and page-related element creation functions
 */

import { applyCommonProperties } from "../utils/nodeUtils";

/**
 * Create a slice node from data
 * @param data Slice configuration data
 * @returns Created slice node
 */
export function createSliceFromData(data: any): SliceNode {
  const slice = figma.createSlice();

  // Set size and position
  if (data.width && data.height) {
    slice.resize(data.width, data.height);
  }

  if (data.x !== undefined) slice.x = data.x;
  if (data.y !== undefined) slice.y = data.y;

  // Apply export settings
  if (data.exportSettings && Array.isArray(data.exportSettings)) {
    slice.exportSettings = data.exportSettings;
  }

  // Apply common properties that apply to slices
  if (data.name) slice.name = data.name;
  if (data.visible !== undefined) slice.visible = data.visible;

  return slice;
}

/**
 * Create a page node from data
 * @param data Page configuration data
 * @returns Created page node
 */
export function createPageFromData(data: any): PageNode {
  const page = figma.createPage();

  // Set page name
  if (data.name) page.name = data.name;

  // Set background color if provided
  if (data.backgrounds) page.backgrounds = data.backgrounds;

  return page;
}

/**
 * Create a page divider (used for sections)
 * @param data Page divider configuration data
 * @returns Created page divider node
 */
export function createPageDividerFromData(data: any) {
  // Check if this method is available in the current Figma version
  if (!("createPageDivider" in figma)) {
    console.error("createPageDivider is not supported in this Figma version");
    return null;
  }

  try {
    // Using type assertion since API might not be recognized in all Figma versions
    const pageDivider = (figma as any).createPageDivider();

    // Set properties
    if (data.name) pageDivider.name = data.name;

    return pageDivider;
  } catch (error) {
    console.error("Failed to create page divider:", error);
    return null;
  }
}

/**
 * Create a slide node (for Figma Slides)
 * @param data Slide configuration data
 * @returns Created slide node
 */
export function createSlideFromData(data: any): SlideNode | null {
  // Check if this method is available in the current Figma version
  if (!("createSlide" in figma)) {
    console.error("createSlide is not supported in this Figma version");
    return null;
  }

  try {
    // Using type assertion since API might not be recognized
    const slide = (figma as any).createSlide();

    // Set slide properties
    if (data.name) slide.name = data.name;

    // Apply common properties
    applyCommonProperties(slide, data);

    return slide;
  } catch (error) {
    console.error("Failed to create slide:", error);
    return null;
  }
}

/**
 * Create a slide row node (for Figma Slides)
 * @param data Slide row configuration data
 * @returns Created slide row node
 */
export function createSlideRowFromData(data: any): SlideRowNode | null {
  // Check if this method is available in the current Figma version
  if (!("createSlideRow" in figma)) {
    console.error("createSlideRow is not supported in this Figma version");
    return null;
  }

  try {
    // Using type assertion since API might not be recognized
    const slideRow = (figma as any).createSlideRow();

    // Set slide row properties
    if (data.name) slideRow.name = data.name;

    // Apply common properties
    applyCommonProperties(slideRow, data);

    return slideRow;
  } catch (error) {
    console.error("Failed to create slide row:", error);
    return null;
  }
}

```

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

```typescript
/**
 * Widget Tools - Utility functions for Figma widget development
 */

// Theme constants
export const COLORS = {
  primary: "#0D99FF",
  primaryHover: "#0870B8",
  secondary: "#F0F0F0",
  secondaryHover: "#E0E0E0",
  text: "#333333",
  lightText: "#666666",
  background: "#FFFFFF",
  border: "#E6E6E6",
  success: "#36B37E",
  error: "#FF5630",
  warning: "#FFAB00",
};

// Widget sizing helpers
export const SPACING = {
  xs: 4,
  sm: 8, 
  md: 16,
  lg: 24,
  xl: 32,
};

// Common shadow effects
export const EFFECTS = {
  dropShadow: {
    type: "drop-shadow" as const,
    color: { r: 0, g: 0, b: 0, a: 0.1 },
    offset: { x: 0, y: 2 },
    blur: 4,
  },
  strongShadow: {
    type: "drop-shadow" as const,
    color: { r: 0, g: 0, b: 0, a: 0.2 },
    offset: { x: 0, y: 4 },
    blur: 8,
  }
};

// Formatting helpers
export const formatDate = (date: Date): string => {
  return date.toLocaleDateString('en-US', {
    year: 'numeric',
    month: 'short',
    day: 'numeric'
  });
};

export const truncateText = (text: string, maxLength: number = 100): string => {
  if (text.length <= maxLength) return text;
  return text.substring(0, maxLength) + '...';
};

// Figma widget helper functions
export const createNodeId = (): string => {
  return 'id_' + Math.random().toString(36).substring(2, 11);
};

// UI Component generators
export type ButtonVariant = 'primary' | 'secondary' | 'danger';

export const buttonStyles = (variant: ButtonVariant = 'primary') => {
  switch (variant) {
    case 'primary':
      return {
        fill: COLORS.primary,
        hoverFill: COLORS.primaryHover,
        textColor: '#FFFFFF',
      };
    case 'secondary':
      return {
        fill: COLORS.secondary,
        hoverFill: COLORS.secondaryHover,
        textColor: COLORS.text,
      };
    case 'danger':
      return {
        fill: COLORS.error,
        hoverFill: '#E64C3D',
        textColor: '#FFFFFF',
      };
    default:
      return {
        fill: COLORS.primary,
        hoverFill: COLORS.primaryHover,
        textColor: '#FFFFFF',
      };
  }
};

// Network request utilities for widgets
export const fetchWithTimeout = async (
  url: string, 
  options: RequestInit = {}, 
  timeout: number = 10000
): Promise<Response> => {
  const controller = new AbortController();
  const id = setTimeout(() => controller.abort(), timeout);
  
  try {
    const response = await fetch(url, {
      ...options,
      signal: controller.signal
    });
    clearTimeout(id);
    return response;
  } catch (error) {
    clearTimeout(id);
    throw error;
  }
};

// Storage helpers
export const saveToLocalStorage = (key: string, data: any): void => {
  try {
    localStorage.setItem(key, JSON.stringify(data));
  } catch (error) {
    console.error('Error saving to localStorage:', error);
  }
};

export const getFromLocalStorage = <T>(key: string, defaultValue: T): T => {
  try {
    const item = localStorage.getItem(key);
    return item ? JSON.parse(item) : defaultValue;
  } catch (error) {
    console.error('Error reading from localStorage:', error);
    return defaultValue;
  }
};

// Widget data utilities
export interface WidgetData {
  id: string;
  name: string;
  createdAt: string;
  updatedAt: string;
  data: Record<string, any>;
}

export const createWidgetData = (name: string, data: Record<string, any> = {}): WidgetData => {
  const now = new Date().toISOString();
  return {
    id: createNodeId(),
    name,
    createdAt: now,
    updatedAt: now,
    data
  };
};

export const updateWidgetData = (widgetData: WidgetData, newData: Partial<Record<string, any>>): WidgetData => {
  return {
    ...widgetData,
    updatedAt: new Date().toISOString(),
    data: { ...widgetData.data, ...newData }
  };
};

```

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

```typescript
/**
 * Tool: search_widgets
 * 
 * Searches for widgets that have specific sync data properties and values
 */
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import figmaApi from "../../services/figma-api.js";
import { FigmaUtils } from "../../utils/figma-utils.js";

export const searchWidgetsTool = (server: McpServer) => {
  server.tool(
    "search_widgets",
    {
      file_key: z.string().min(1).describe("The Figma file key"),
      property_key: z.string().min(1).describe("The sync data property key to search for"),
      property_value: z.string().optional().describe("Optional property value to match (if not provided, returns all widgets with the property)")
    },
    async ({ file_key, property_key, property_value }) => {
      try {
        const file = await figmaApi.getFile(file_key);
        
        // Find all widget nodes
        const allWidgetNodes = FigmaUtils.getNodesByType(file, 'WIDGET');
        
        // Filter widgets that have the specified property
        const matchingWidgets = allWidgetNodes.filter(node => {
          if (!node.widgetSync) return false;
          
          try {
            const syncData = JSON.parse(node.widgetSync);
            
            // If property_value is provided, check for exact match
            if (property_value !== undefined) {
              // Handle different types of values (string, number, boolean)
              const propValue = syncData[property_key];
              
              if (typeof propValue === 'string') {
                return propValue === property_value;
              } else if (typeof propValue === 'number') {
                return propValue.toString() === property_value;
              } else if (typeof propValue === 'boolean') {
                return propValue.toString() === property_value;
              } else if (propValue !== null && typeof propValue === 'object') {
                return JSON.stringify(propValue) === property_value;
              }
              
              return false;
            }
            
            // If no value provided, just check if the property exists
            return property_key in syncData;
          } catch (error) {
            return false;
          }
        });
        
        if (matchingWidgets.length === 0) {
          return {
            content: [
              { type: "text", text: property_value ? 
                `No widgets found with property "${property_key}" = "${property_value}"` : 
                `No widgets found with property "${property_key}"` 
              }
            ]
          };
        }
        
        const widgetsList = matchingWidgets.map((node, index) => {
          let syncDataValue = '';
          try {
            const syncData = JSON.parse(node.widgetSync!);
            const value = syncData[property_key];
            syncDataValue = typeof value === 'object' ? 
              JSON.stringify(value) : 
              String(value);
          } catch (error) {
            syncDataValue = 'Error parsing sync data';
          }
          
          return `${index + 1}. **${node.name}** (ID: ${node.id})
   - Property "${property_key}": ${syncDataValue}`;
        }).join('\n\n');
        
        return {
          content: [
            { type: "text", text: property_value ? 
              `# Widgets with property "${property_key}" = "${property_value}"` : 
              `# Widgets with property "${property_key}"` 
            },
            { type: "text", text: `Found ${matchingWidgets.length} matching widgets:` },
            { type: "text", text: widgetsList }
          ]
        };
      } catch (error) {
        console.error('Error searching widgets:', error);
        return {
          content: [
            { type: "text", text: `Error searching widgets: ${(error as Error).message}` }
          ]
        };
      }
    }
  );
};

```

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

```markdown
# Implementation Steps

This document outlines the process followed to implement the Figma MCP server, from project setup to final testing.

## 1. Project Setup

### Initial Directory Structure

The project was organized with the following structure:
- `/src` for TypeScript source files
- `/config` for configuration files
- `/services` for API integrations
- `/utils` for utility functions
- `/types` for type definitions

### Dependencies Installation

The project uses Bun as its package manager and runtime. Key dependencies include:
- `@modelcontextprotocol/sdk` for MCP implementation
- `@figma/rest-api-spec` for Figma API type definitions
- `axios` for HTTP requests
- `zod` for schema validation
- `dotenv` for environment variable management

## 2. Configuration Setup

### Environment Variables

Created a configuration system using Zod to validate environment variables:
- `FIGMA_PERSONAL_ACCESS_TOKEN`: For Figma API authentication
- `PORT`: Server port (default: 3001)
- `NODE_ENV`: Environment (development/production)

## 3. Figma API Integration

### API Service Implementation

Created a comprehensive service for interacting with the Figma API:
- Used official Figma REST API specification types
- Implemented methods for all required API endpoints
- Added request/response handling with proper error management
- Organized methods by resource type (files, nodes, comments, etc.)

### Utility Functions

Implemented utility functions for common operations:
- Finding nodes by ID
- Getting nodes by type
- Extracting text from nodes
- Formatting node information
- Calculating node paths in document hierarchy

## 4. MCP Server Implementation

### Server Setup

Set up the MCP server using the MCP SDK:
- Configured server metadata (name, version)
- Connected to standard I/O for communication
- Set up error handling and logging

### Tools Implementation

Created tools for various Figma operations:
- `get_file`: Retrieve file information
- `get_node`: Access specific nodes
- `get_comments`: Read file comments
- `get_images`: Export node images
- `get_file_versions`: Access version history
- `search_text`: Search for text in files
- `get_components`: Get file components
- `add_comment`: Add comments to files

Each tool includes:
- Parameter validation using Zod
- Error handling and response formatting
- Proper response formatting for AI consumption

### Resource Templates

Implemented resource templates for consistent access patterns:
- `figma-file://{file_key}`: Access to Figma files
- `figma-node://{file_key}/{node_id}`: Access to specific nodes

## 5. Build System

### Build Configuration

Set up a build system with Bun:
- Configured TypeScript compilation
- Set up build scripts for development and production
- Created a Makefile for common operations

### Scripts

Implemented various scripts:
- `start`: Run the server
- `dev`: Development mode with auto-reload
- `mcp`: MCP server with auto-reload
- `build`: Build the project
- `build:mcp`: Build the MCP server
- `test`: Run tests
- `clean`: Clean build artifacts

## 6. Documentation

### README

Created a comprehensive README with:
- Project description
- Installation instructions
- Usage examples
- Available tools and resources
- Development guidelines

### Code Documentation

Added documentation throughout the codebase:
- Function and method descriptions
- Parameter documentation
- Type definitions
- Usage examples

## 7. Testing and Verification

### Build Verification

Verified the build process:
- Confirmed successful compilation
- Checked for TypeScript errors
- Ensured all dependencies were properly resolved

### File Structure Verification

Confirmed the final directory structure:
- All required files in place
- Proper organization of code
- Correct file permissions

## Next Steps

- **Integration Testing**: Test with real AI assistants
- **Performance Optimization**: Optimize for response time
- **Caching**: Add caching for frequent requests
- **Extended Capabilities**: Add more tools and resources
- **User Documentation**: Create end-user documentation

```

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

```markdown
# Project Status and Roadmap

This document tracks the current status of the Figma MCP server project and outlines future development plans.

## 1. Current Status

### Completed Tasks

✅ **Project Setup**
- Created project structure
- Installed dependencies
- Configured TypeScript environment
- Set up build system

✅ **Core Components**
- Environment configuration
- Figma API service
- Utility functions
- MCP server implementation

✅ **MCP Tools**
- get_file: Retrieve Figma files
- get_node: Access specific nodes
- get_comments: Read file comments
- get_images: Export node images
- get_file_versions: Access version history
- search_text: Search for text in files
- get_components: Get file components
- add_comment: Add comments to files

✅ **Resource Templates**
- figma-file: Access to Figma files
- figma-node: Access to specific nodes

✅ **Documentation**
- Project overview
- Implementation steps
- Components and features
- Usage guide
- Project status and roadmap

### Current Limitations

- No authentication refresh mechanism
- Limited error reporting detail
- No caching mechanism for frequent requests
- Limited support for advanced Figma features
- No pagination support for large result sets
- Limited testing

## 2. Next Steps

### Short-Term Goals (Next 2-4 Weeks)

- [ ] **Comprehensive Testing**
  - Unit tests for all components
  - Integration tests with Figma API
  - Performance testing

- [ ] **Error Handling Improvements**
  - More detailed error messages
  - Better error categorization
  - Recovery mechanisms

- [ ] **Caching System**
  - Implement response caching
  - Configure TTL for different resource types
  - Cache invalidation mechanisms

- [ ] **Authentication Enhancements**
  - Token refresh mechanism
  - Better error handling for authentication issues
  - Support for OAuth authentication

### Medium-Term Goals (Next 2-3 Months)

- [ ] **Additional Tools**
  - Team and project management
  - Style operations
  - Branch management
  - Widget interactions
  - Variable access and manipulation

- [ ] **Enhanced Resource Templates**
  - More granular resource access
  - Improved filtering and searching
  - Resource relationships

- [ ] **Performance Optimizations**
  - Parallel request processing
  - Response size optimization
  - Processing time improvements

- [ ] **Security Enhancements**
  - Request validation
  - Rate limiting
  - Access control for sensitive operations

### Long-Term Goals (3+ Months)

- [ ] **Advanced Feature Support**
  - FigJam-specific features
  - Prototyping capabilities
  - Dev mode integration
  - Widget creation and management

- [ ] **Real-Time Updates**
  - Webhook integration for file changes
  - Live updates for collaborative editing

- [ ] **Extended Integration**
  - Integration with other design tools
  - Version control system integration
  - CI/CD pipeline integration

- [ ] **Advanced AI Features**
  - Design analysis capabilities
  - Automated design suggestions
  - Design consistency checking

## 3. Version History

### v1.0.0 (April 13, 2025)
- Initial release
- Core tools and resources
- Basic documentation

## 4. Known Issues

- Large files may cause performance issues
- Certain complex node types may not be fully supported
- Error handling in nested operations needs improvement
- Some API rate limits may be encountered with frequent use

## 5. Contribution Guidelines

### Priority Areas for Contribution

1. **Testing**: Unit and integration tests
2. **Documentation**: Usage examples and API docs
3. **Feature Expansion**: Additional tools and resources
4. **Performance**: Optimizations for large files and complex operations
5. **Error Handling**: Improved error reporting and recovery

### Contribution Process

1. Select an issue or feature from the project board
2. Create a branch with a descriptive name
3. Implement the change with appropriate tests
4. Submit a pull request with a clear description
5. Address review feedback
6. Merge upon approval

## 6. Support and Feedback

For support or to provide feedback, please:
- Open an issue in the GitHub repository
- Contact the project maintainers
- Join the project discussion forum

---

Last updated: April 13, 2025

```

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

```typescript
/**
 * Service for interacting with Figma Widget API
 */
import axios from 'axios';
import { env } from '../config/env.js';
import { z } from 'zod';

const FIGMA_API_BASE_URL = 'https://api.figma.com/v1';

// Widget data schemas
export const WidgetNodeSchema = z.object({
  id: z.string(),
  name: z.string(),
  type: z.literal('WIDGET'),
  widgetId: z.string().optional(),
  widgetSync: z.string().optional(),
  pluginData: z.record(z.unknown()).optional(),
  sharedPluginData: z.record(z.record(z.unknown())).optional(),
});

export type WidgetNode = z.infer<typeof WidgetNodeSchema>;

export const WidgetSyncDataSchema = z.record(z.unknown());
export type WidgetSyncData = z.infer<typeof WidgetSyncDataSchema>;

/**
 * Service for interacting with Figma Widget API
 */
export class WidgetApiService {
  private readonly headers: Record<string, string>;

  constructor(accessToken: string = env.FIGMA_PERSONAL_ACCESS_TOKEN) {
    this.headers = {
      'X-Figma-Token': accessToken,
    };
  }

  /**
   * Get all widget nodes in a file
   */
  async getWidgetNodes(fileKey: string): Promise<WidgetNode[]> {
    try {
      const response = await axios.get(, {
        headers: this.headers,
      });
      
      const file = response.data;
      return this.findAllWidgetNodes(file.document);
    } catch (error) {
      console.error('Error fetching widget nodes:', error);
      throw error;
    }
  }

  /**
   * Get a specific widget node by ID
   */
  async getWidgetNode(fileKey: string, nodeId: string): Promise<WidgetNode | null> {
    try {
      const response = await axios.get(, {
        headers: this.headers,
      });
      
      const node = response.data.nodes[nodeId]?.document;
      if (!node || node.type !== 'WIDGET') {
        return null;
      }
      
      return WidgetNodeSchema.parse(node);
    } catch (error) {
      console.error('Error fetching widget node:', error);
      throw error;
    }
  }

  /**
   * Get the widget sync data (state) for a specific widget
   */
  async getWidgetSyncData(fileKey: string, nodeId: string): Promise<WidgetSyncData | null> {
    try {
      const widgetNode = await this.getWidgetNode(fileKey, nodeId);
      
      if (!widgetNode || !widgetNode.widgetSync) {
        return null;
      }
      
      // Parse the widgetSync data string (it's stored as a JSON string)
      try {
        return JSON.parse(widgetNode.widgetSync);
      } catch (parseError) {
        console.error('Error parsing widget sync data:', parseError);
        return null;
      }
    } catch (error) {
      console.error('Error fetching widget sync data:', error);
      throw error;
    }
  }

  /**
   * Create a widget instance in a file (requires special access)
   * Note: This is only available to Figma widget developers or partners.
   */
  async createWidget(fileKey: string, options: {
    name: string,
    widgetId: string,
    x: number,
    y: number,
    initialSyncData?: Record<string, any>,
    parentNodeId?: string,
  }): Promise<{ widgetNodeId: string } | null> {
    try {
      // This endpoint might not be publicly available
      const response = await axios.post(
        ,
        options,
        { headers: this.headers }
      );
      
      return response.data;
    } catch (error) {
      console.error('Error creating widget:', error);
      throw error;
    }
  }

  /**
   * Update a widget's properties (requires widget developer access)
   * Note: This functionality is limited to Figma widget developers.
   */
  async updateWidgetProperties(fileKey: string, nodeId: string, properties: Record<string, any>): Promise<boolean> {
    try {
      // This endpoint might not be publicly available
      await axios.patch(
        ,
        { properties },
        { headers: this.headers }
      );
      
      return true;
    } catch (error) {
      console.error('Error updating widget properties:', error);
      throw error;
    }
  }

  /**
   * Helper method to recursively find all widget nodes in a file
   */
  private findAllWidgetNodes(node: any): WidgetNode[] {
    let widgets: WidgetNode[] = [];
    
    if (node.type === 'WIDGET') {
      try {
        widgets.push(WidgetNodeSchema.parse(node));
      } catch (error) {
        console.error('Error parsing widget node:', error);
      }
    }
    
    if (node.children) {
      for (const child of node.children) {
        widgets = widgets.concat(this.findAllWidgetNodes(child));
      }
    }
    
    return widgets;
  }
}

export default new WidgetApiService();

```

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

```typescript
/**
 * Resources for the Figma MCP server
 */
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
import figmaApi from "./services/figma-api.js";

/**
 * Resource template for Figma files
 */
export const figmaFileResource = (server: McpServer): void => {
  server.resource(
    "figma-file",
    new ResourceTemplate("figma-file://{file_key}", {
      // Define listCallback instead of just providing a string for 'list'
      listCallback: async () => {
        try {
          // Here we would typically get a list of files
          // For now, return an empty list since we don't have access to "all files"
          return {
            contents: [{
              uri: "figma-file://",
              title: "Figma Files",
              description: "List of Figma files you have access to",
              text: "# Figma Files\n\nTo access a specific file, you need to provide its file key."
            }]
          };
        } catch (error) {
          console.error('Error listing files:', error);
          return {
            contents: [{
              uri: "figma-file://",
              title: "Error listing files",
              text: `Error: ${(error as Error).message}`
            }]
          };
        }
      }
    }),
    async (uri, { file_key }) => {
      try {
        const file = await figmaApi.getFile(file_key);
        
        return {
          contents: [{
            uri: uri.href,
            title: file.name,
            description: `Last modified: ${file.lastModified}`,
            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}`
          }]
        };
      } catch (error) {
        console.error('Error fetching file for resource:', error);
        return {
          contents: [{
            uri: uri.href,
            title: `File not found: ${file_key}`,
            text: `Error: ${(error as Error).message}`
          }]
        };
      }
    }
  );
};

/**
 * Resource template for Figma nodes
 */
export const figmaNodeResource = (server: McpServer): void => {
  server.resource(
    "figma-node",
    new ResourceTemplate("figma-node://{file_key}/{node_id}", {
      // Define listCallback instead of just providing a string for 'list'
      listCallback: async (uri, { file_key }) => {
        try {
          // If only file_key is provided, list all top-level nodes
          const file = await figmaApi.getFile(file_key);
          
          return {
            contents: file.document.children?.map(node => ({
              uri: `figma-node://${file_key}/${node.id}`,
              title: node.name,
              description: `Type: ${node.type}`,
              text: `# ${node.name}\n\nType: ${node.type}\nID: ${node.id}`
            })) || []
          };
        } catch (error) {
          console.error('Error listing nodes:', error);
          return {
            contents: [{
              uri: `figma-node://${file_key}`,
              title: "Error listing nodes",
              text: `Error: ${(error as Error).message}`
            }]
          };
        }
      }
    }),
    async (uri, { file_key, node_id }) => {
      try {
        // Get specific node
        const fileNodes = await figmaApi.getFileNodes(file_key, [node_id]);
        const nodeData = fileNodes.nodes[node_id];
        
        if (!nodeData) {
          return {
            contents: [{
              uri: uri.href,
              title: `Node not found: ${node_id}`,
              text: `Node ${node_id} not found in file ${file_key}`
            }]
          };
        }
        
        return {
          contents: [{
            uri: uri.href,
            title: nodeData.document.name,
            description: `Type: ${nodeData.document.type}`,
            text: `# ${nodeData.document.name}\n\nType: ${nodeData.document.type}\nID: ${nodeData.document.id}\nChildren: ${nodeData.document.children?.length || 0}`
          }]
        };
      } catch (error) {
        console.error('Error fetching node for resource:', error);
        return {
          contents: [{
            uri: uri.href,
            title: `Error`,
            text: `Error: ${(error as Error).message}`
          }]
        };
      }
    }
  );
};

/**
 * Registers all resources with the MCP server
 */
export function registerAllResources(server: McpServer): void {
  figmaFileResource(server);
  figmaNodeResource(server);
}

```

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

```typescript
/**
 * Image and media element creation functions for Figma plugin
 */

import { applyCommonProperties } from '../utils/nodeUtils';

/**
 * Create an image node from data
 * @param data Image configuration data
 * @returns Created image node
 */
export function createImageFromData(data: any): SceneNode | null {
  try {
    // Image creation requires a hash
    if (!data.hash) {
      console.error('Image creation requires an image hash');
      return null;
    }
    
    const image = figma.createImage(data.hash);
    
    // Create a rectangle to display the image
    const rect = figma.createRectangle();
    
    // Set size
    if (data.width && data.height) {
      rect.resize(data.width, data.height);
    }
    
    // Apply image as fill
    rect.fills = [{
      type: 'IMAGE',
      scaleMode: data.scaleMode || 'FILL',
      imageHash: image.hash
    }];
    
    // Apply common properties
    applyCommonProperties(rect, data);
    
    return rect;
  } catch (error) {
    console.error('Failed to create image:', error);
    return null;
  }
}

/**
 * Create an image node asynchronously from data
 * @param data Image configuration data with bytes or file
 * @returns Promise resolving to created image node
 */
export async function createImageFromBytesAsync(data: any): Promise<SceneNode | null> {
  try {
    // Image creation requires bytes or a file
    if (!data.bytes && !data.file) {
      console.error('Image creation requires image bytes or file');
      return null;
    }
    
    let image;
    if (data.bytes) {
      image = await figma.createImageAsync(data.bytes);
    } else if (data.file) {
      // Note: file would need to be provided through some UI interaction
      // as plugins cannot directly access the file system
      image = await figma.createImageAsync(data.file);
    } else {
      return null;
    }
    
    // Create a rectangle to display the image
    const rect = figma.createRectangle();
    
    // Set size
    if (data.width && data.height) {
      rect.resize(data.width, data.height);
    }
    
    // Apply image as fill
    rect.fills = [{
      type: 'IMAGE',
      scaleMode: data.scaleMode || 'FILL',
      imageHash: image.hash
    }];
    
    // Apply common properties
    applyCommonProperties(rect, data);
    
    return rect;
  } catch (error) {
    console.error('Failed to create image asynchronously:', error);
    return null;
  }
}

/**
 * Create a GIF node from data
 * @param data GIF configuration data
 * @returns Created gif node
 */
export function createGifFromData(data: any): SceneNode | null {
  // As of my knowledge, there isn't a direct createGif API
  // Even though it's in the list of methods
  // For now, return null and log an error
  console.error('createGif API is not directly available or implemented');
  return null;
}

/**
 * Create a video node asynchronously from data
 * This depends on figma.createVideoAsync which may not be available in all versions
 * 
 * @param data Video configuration data
 * @returns Promise resolving to created video node
 */
export async function createVideoFromDataAsync(data: any): Promise<SceneNode | null> {
  // Check if video creation is supported
  if (!('createVideoAsync' in figma)) {
    console.error('Video creation is not supported in this Figma version');
    return null;
  }
  
  try {
    // Video creation requires bytes
    if (!data.bytes) {
      console.error('Video creation requires video bytes');
      return null;
    }
    
    // Using type assertion since createVideoAsync may not be recognized by TypeScript
    const video = await (figma as any).createVideoAsync(data.bytes);
    
    // Apply common properties
    applyCommonProperties(video, data);
    
    return video;
  } catch (error) {
    console.error('Failed to create video:', error);
    return null;
  }
}

/**
 * Create a link preview node asynchronously from data
 * This depends on figma.createLinkPreviewAsync which may not be available in all versions
 * 
 * @param data Link preview configuration data
 * @returns Promise resolving to created link preview node
 */
export async function createLinkPreviewFromDataAsync(data: any): Promise<SceneNode | null> {
  // Check if link preview creation is supported
  if (!('createLinkPreviewAsync' in figma)) {
    console.error('Link preview creation is not supported in this Figma version');
    return null;
  }
  
  try {
    // Link preview creation requires a URL
    if (!data.url) {
      console.error('Link preview creation requires a URL');
      return null;
    }
    
    // Using type assertion since createLinkPreviewAsync may not be recognized by TypeScript
    const linkPreview = await (figma as any).createLinkPreviewAsync(data.url);
    
    // Apply common properties
    applyCommonProperties(linkPreview, data);
    
    return linkPreview;
  } catch (error) {
    console.error('Failed to create link preview:', error);
    return null;
  }
} 
```

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

```markdown
# Components and Features

This document provides detailed information about the key components and features of the Figma MCP server.

## 1. Core Components

### Environment Configuration (`src/config/env.ts`)

The environment configuration component:

- Loads variables from `.env` file using dotenv
- Validates environment variables using Zod schema
- Provides type-safe access to configuration values
- Ensures required variables are present
- Sets sensible defaults for optional variables

```typescript
// Example of environment validation
const envSchema = z.object({
  FIGMA_PERSONAL_ACCESS_TOKEN: z.string().min(1),
  PORT: z.string().default("3001").transform(Number),
  NODE_ENV: z
    .enum(["development", "production", "test"])
    .default("development"),
});
```

### Figma API Service (`src/services/figma-api.ts`)

A comprehensive service for interacting with the Figma API:

- Uses official Figma API TypeScript definitions
- Provides methods for all relevant API endpoints
- Handles authentication and request formatting
- Processes responses and errors consistently
- Supports all Figma resource types:
  - Files and nodes
  - Comments
  - Images
  - Components and styles
  - Versions
  - Teams and projects

```typescript
// Example method for retrieving a Figma file
async getFile(fileKey: string, params: {
  ids?: string;
  depth?: number;
  geometry?: string
} = {}): Promise<GetFileResponse> {
  const response = await axios.get(`${FIGMA_API_BASE_URL}/files/${fileKey}`, {
    headers: this.headers,
    params,
  });
  return response.data;
}
```

### Figma Utilities (`src/utils/figma-utils.ts`)

Utility functions for working with Figma data:

- Node search and traversal
- Text extraction
- Property formatting
- Path calculation
- Type-specific operations

```typescript
// Example utility for finding a node by ID
static findNodeById(file: GetFileResponse, nodeId: string): Node | null {
  // Implementation details...
}
```

### MCP Server Implementation (`src/index.ts`)

The main MCP server implementation:

- Configures the MCP server
- Defines tools and resources
- Handles communication via standard I/O
- Manages error handling and response formatting

```typescript
// Example of MCP server configuration
const server = new McpServer({
  name: "Figma API",
  version: "1.0.0",
});
```

## 2. MCP Tools

The server provides the following tools:

### `get_file`

Retrieves a Figma file by key:

- Parameters:
  - `file_key`: The Figma file key
  - `return_full_file`: Whether to return the full file structure
- Returns:
  - File name, modification date
  - Document structure summary
  - Component and style counts
  - Full file contents (if requested)

### `get_node`

Retrieves a specific node from a Figma file:

- Parameters:
  - `file_key`: The Figma file key
  - `node_id`: The ID of the node to retrieve
- Returns:
  - Node name, type, and ID
  - Node properties and attributes
  - Child node count

### `get_comments`

Retrieves comments from a Figma file:

- Parameters:
  - `file_key`: The Figma file key
- Returns:
  - Comment count
  - Comment text
  - Author information
  - Timestamps

### `get_images`

Exports nodes as images:

- Parameters:
  - `file_key`: The Figma file key
  - `node_ids`: Array of node IDs to export
  - `format`: Image format (jpg, png, svg, pdf)
  - `scale`: Scale factor for the image
- Returns:
  - Image URLs for each node
  - Error information for failed exports

### `get_file_versions`

Retrieves version history for a file:

- Parameters:
  - `file_key`: The Figma file key
- Returns:
  - Version list
  - Version labels and descriptions
  - Author information
  - Timestamps

### `search_text`

Searches for text within a Figma file:

- Parameters:
  - `file_key`: The Figma file key
  - `search_text`: The text to search for
- Returns:
  - Matching text nodes
  - Node paths in document hierarchy
  - Matching text content

### `get_components`

Retrieves components from a Figma file:

- Parameters:
  - `file_key`: The Figma file key
- Returns:
  - Component list
  - Component names and keys
  - Component descriptions
  - Remote status

### `add_comment`

Adds a comment to a Figma file:

- Parameters:
  - `file_key`: The Figma file key
  - `message`: The comment text
  - `node_id`: Optional node ID to attach the comment to
- Returns:
  - Comment ID
  - Author information
  - Timestamp

## 3. Resource Templates

The server provides the following resource templates:

### `figma-file://{file_key}`

Provides access to Figma files:

- URI format: `figma-file://{file_key}`
- List URI: `figma-file://`
- Returns:
  - File name
  - Last modified date
  - Document structure summary

### `figma-node://{file_key}/{node_id}`

Provides access to nodes within Figma files:

- URI format: `figma-node://{file_key}/{node_id}`
- List URI: `figma-node://{file_key}`
- Returns:
  - Node name and type
  - Node properties
  - Child node count

## 4. Error Handling

The server implements comprehensive error handling:

- API request errors
- Authentication failures
- Invalid parameters
- Resource not found errors
- Server errors

Each error is properly formatted and returned to the client with:

- Error message
- Error type
- Context information (when available)

## 5. Response Formatting

Responses are formatted for optimal consumption by AI assistants:

- Clear headings
- Structured information
- Formatted lists
- Contextual descriptions
- Links and references where appropriate

```

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

```markdown
# Usage Guide

This document provides detailed instructions for setting up, running, and using the Figma MCP server.

## 1. Setup Instructions

### Prerequisites

Before you begin, ensure you have the following:
- [Bun](https://bun.sh/) v1.0.0 or higher installed
- A Figma account with API access
- A personal access token from Figma

### Installation

1. Clone the repository:
   ```bash
   git clone <repository-url>
   cd figma-mcp-server
   ```

2. Install dependencies:
   ```bash
   make install
   ```
   or
   ```bash
   bun install
   ```

3. Configure environment variables:
   - Copy the example environment file:
     ```bash
     cp .env.example .env
     ```
   - Edit `.env` and add your Figma personal access token:
     ```
     FIGMA_PERSONAL_ACCESS_TOKEN=your_figma_token_here
     PORT=3001
     NODE_ENV=development
     ```

## 2. Running the Server

### Development Mode

Run the server in development mode with auto-reload:
```bash
make mcp
```
or
```bash
bun run mcp
```

### Production Mode

1. Build the server:
   ```bash
   make build-mcp
   ```
   or
   ```bash
   bun run build:mcp
   ```

2. Run the built server:
   ```bash
   make start
   ```
   or
   ```bash
   bun run start
   ```

## 3. Using the MCP Tools

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.

### Example: Retrieving a Figma File

Using the `get_file` tool:

```json
{
  "tool": "get_file",
  "parameters": {
    "file_key": "abc123xyz789",
    "return_full_file": false
  }
}
```

Expected response:
```json
{
  "content": [
    { "type": "text", "text": "# Figma File: My Design" },
    { "type": "text", "text": "Last modified: 2025-04-10T15:30:45Z" },
    { "type": "text", "text": "Document contains 5 top-level nodes." },
    { "type": "text", "text": "Components: 12" },
    { "type": "text", "text": "Component sets: 3" },
    { "type": "text", "text": "Styles: 8" }
  ]
}
```

### Example: Searching for Text

Using the `search_text` tool:

```json
{
  "tool": "search_text",
  "parameters": {
    "file_key": "abc123xyz789",
    "search_text": "Welcome"
  }
}
```

Expected response:
```json
{
  "content": [
    { "type": "text", "text": "# Text Search Results for \"Welcome\"" },
    { "type": "text", "text": "Found 2 matching text nodes:" },
    { "type": "text", "text": "- **Header Text** (ID: 123:456)\n  Path: Page 1 > Header > Text\n  Text: \"Welcome to our application\"" }
  ]
}
```

### Example: Adding a Comment

Using the `add_comment` tool:

```json
{
  "tool": "add_comment",
  "parameters": {
    "file_key": "abc123xyz789",
    "message": "This design looks great! Consider adjusting the contrast on the buttons.",
    "node_id": "123:456"
  }
}
```

Expected response:
```json
{
  "content": [
    { "type": "text", "text": "Comment added successfully!" },
    { "type": "text", "text": "Comment ID: 987654" },
    { "type": "text", "text": "By user: John Doe" },
    { "type": "text", "text": "Added at: 4/13/2025, 12:34:56 PM" }
  ]
}
```

## 4. Using Resource Templates

Resource templates provide a consistent way to access Figma resources.

### Example: Accessing a File

Resource URI: `figma-file://abc123xyz789`

Expected response:
```json
{
  "contents": [{
    "uri": "figma-file://abc123xyz789",
    "title": "My Design",
    "description": "Last modified: 2025-04-10T15:30:45Z",
    "text": "# My Design\n\nLast modified: 2025-04-10T15:30:45Z\n\nDocument contains 5 top-level nodes.\nComponents: 12\nStyles: 8"
  }]
}
```

### Example: Listing Nodes in a File

Resource URI: `figma-node://abc123xyz789`

Expected response:
```json
{
  "contents": [
    {
      "uri": "figma-node://abc123xyz789/1:1",
      "title": "Page 1",
      "description": "Type: CANVAS",
      "text": "# Page 1\n\nType: CANVAS\nID: 1:1"
    },
    {
      "uri": "figma-node://abc123xyz789/1:2",
      "title": "Page 2",
      "description": "Type: CANVAS",
      "text": "# Page 2\n\nType: CANVAS\nID: 1:2"
    }
  ]
}
```

### Example: Accessing a Specific Node

Resource URI: `figma-node://abc123xyz789/123:456`

Expected response:
```json
{
  "contents": [{
    "uri": "figma-node://abc123xyz789/123:456",
    "title": "Header Text",
    "description": "Type: TEXT",
    "text": "# Header Text\n\nType: TEXT\nID: 123:456\nChildren: 0"
  }]
}
```

## 5. Error Handling Examples

### Example: File Not Found

```json
{
  "content": [
    { "type": "text", "text": "Error getting Figma file: File not found" }
  ]
}
```

### Example: Node Not Found

```json
{
  "content": [
    { "type": "text", "text": "Node 123:456 not found in file abc123xyz789" }
  ]
}
```

### Example: Authentication Error

```json
{
  "content": [
    { "type": "text", "text": "Error getting Figma file: Authentication failed. Please check your personal access token." }
  ]
}
```

## 6. Tips and Best Practices

1. **File Keys**: Obtain file keys from Figma file URLs. The format is typically `https://www.figma.com/file/FILE_KEY/FILE_NAME`.

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.

3. **Performance**: For large files, use targeted queries with specific node IDs rather than retrieving the entire file.

4. **Image Export**: When exporting images, use appropriate scale factors: 1 for normal resolution, 2 for @2x, etc.

5. **Comments**: When adding comments, provide node IDs to attach comments to specific elements.

6. **Error Handling**: Always handle potential errors in your client application.

7. **Resource Caching**: Consider caching resource responses for improved performance in your client application.

```

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

```typescript
/**
 * Container element creation functions for Figma plugin
 * Including Frame, Component, and other container-like nodes
 */

import { createSolidPaint } from '../utils/colorUtils';
import { applyCommonProperties } from '../utils/nodeUtils';

/**
 * Create a frame from data
 * @param data Frame configuration data
 * @returns Created frame node
 */
export function createFrameFromData(data: any): FrameNode {
  const frame = figma.createFrame();
  
  // Size
  frame.resize(data.width || 100, data.height || 100);
  
  // Background
  if (data.fills) {
    frame.fills = data.fills;
  } else if (data.fill) {
    if (typeof data.fill === 'string') {
      frame.fills = [createSolidPaint(data.fill)];
    } else {
      frame.fills = [data.fill];
    }
  }
  
  // Auto layout properties
  if (data.layoutMode) frame.layoutMode = data.layoutMode;
  if (data.primaryAxisSizingMode) frame.primaryAxisSizingMode = data.primaryAxisSizingMode;
  if (data.counterAxisSizingMode) frame.counterAxisSizingMode = data.counterAxisSizingMode;
  if (data.primaryAxisAlignItems) frame.primaryAxisAlignItems = data.primaryAxisAlignItems;
  if (data.counterAxisAlignItems) frame.counterAxisAlignItems = data.counterAxisAlignItems;
  if (data.paddingLeft !== undefined) frame.paddingLeft = data.paddingLeft;
  if (data.paddingRight !== undefined) frame.paddingRight = data.paddingRight;
  if (data.paddingTop !== undefined) frame.paddingTop = data.paddingTop;
  if (data.paddingBottom !== undefined) frame.paddingBottom = data.paddingBottom;
  if (data.itemSpacing !== undefined) frame.itemSpacing = data.itemSpacing;
  
  // Corner radius
  if (data.cornerRadius !== undefined) frame.cornerRadius = data.cornerRadius;
  if (data.topLeftRadius !== undefined) frame.topLeftRadius = data.topLeftRadius;
  if (data.topRightRadius !== undefined) frame.topRightRadius = data.topRightRadius;
  if (data.bottomLeftRadius !== undefined) frame.bottomLeftRadius = data.bottomLeftRadius;
  if (data.bottomRightRadius !== undefined) frame.bottomRightRadius = data.bottomRightRadius;
  
  return frame;
}

/**
 * Create a component from data
 * @param data Component configuration data
 * @returns Created component node
 */
export function createComponentFromData(data: any): ComponentNode {
  const component = figma.createComponent();
  
  // Size
  component.resize(data.width || 100, data.height || 100);
  
  // Background
  if (data.fills) {
    component.fills = data.fills;
  } else if (data.fill) {
    if (typeof data.fill === 'string') {
      component.fills = [createSolidPaint(data.fill)];
    } else {
      component.fills = [data.fill];
    }
  }
  
  // Auto layout properties (components support same auto layout as frames)
  if (data.layoutMode) component.layoutMode = data.layoutMode;
  if (data.primaryAxisSizingMode) component.primaryAxisSizingMode = data.primaryAxisSizingMode;
  if (data.counterAxisSizingMode) component.counterAxisSizingMode = data.counterAxisSizingMode;
  if (data.primaryAxisAlignItems) component.primaryAxisAlignItems = data.primaryAxisAlignItems;
  if (data.counterAxisAlignItems) component.counterAxisAlignItems = data.counterAxisAlignItems;
  if (data.paddingLeft !== undefined) component.paddingLeft = data.paddingLeft;
  if (data.paddingRight !== undefined) component.paddingRight = data.paddingRight;
  if (data.paddingTop !== undefined) component.paddingTop = data.paddingTop;
  if (data.paddingBottom !== undefined) component.paddingBottom = data.paddingBottom;
  if (data.itemSpacing !== undefined) component.itemSpacing = data.itemSpacing;
  
  // Component properties
  if (data.description) component.description = data.description;
  
  return component;
}

/**
 * Create a group from data
 * Note: Groups require children, so this typically needs to be used after creating child nodes
 * 
 * @param data Group configuration data
 * @param children Child nodes to include in the group
 * @returns Created group node
 */
export function createGroupFromData(data: any, children: SceneNode[]): GroupNode {
  // Create group with the provided children
  const group = figma.group(children, figma.currentPage);
  
  // Apply common properties
  applyCommonProperties(group, data);
  
  return group;
}

/**
 * Create an instance from data
 * @param data Instance configuration data (must include componentId)
 * @returns Created instance node
 */
export function createInstanceFromData(data: any): InstanceNode | null {
  if (!data.componentId) {
    console.error('Cannot create instance: componentId is required');
    return null;
  }
  
  // Try to find the component
  const component = figma.getNodeById(data.componentId) as ComponentNode;
  if (!component || component.type !== 'COMPONENT') {
    console.error(`Cannot create instance: component with id ${data.componentId} not found`);
    return null;
  }
  
  // Create instance
  const instance = component.createInstance();
  
  // Apply common properties
  applyCommonProperties(instance, data);
  
  // Handle instance-specific properties
  if (data.componentProperties) {
    for (const [key, value] of Object.entries(data.componentProperties)) {
      if (key in instance.componentProperties) {
        // Handle different types of component properties
        const prop = instance.componentProperties[key];
        if (prop.type === 'BOOLEAN') {
          instance.setProperties({ [key]: !!value });
        } else if (prop.type === 'TEXT') {
          instance.setProperties({ [key]: String(value) });
        } else if (prop.type === 'INSTANCE_SWAP') {
          instance.setProperties({ [key]: String(value) });
        } else if (prop.type === 'VARIANT') {
          instance.setProperties({ [key]: String(value) });
        }
      }
    }
  }
  
  return instance;
}

/**
 * Create a section from data
 * Sections are a special type of node used to organize frames in Figma
 * 
 * @param data Section configuration data
 * @returns Created section node
 */
export function createSectionFromData(data: any): SectionNode {
  const section = figma.createSection();
  
  // Section-specific properties
  if (data.name) section.name = data.name;
  if (data.sectionContentsHidden !== undefined) section.sectionContentsHidden = data.sectionContentsHidden;
  
  // Apply common properties that apply to sections
  if (data.x !== undefined) section.x = data.x;
  if (data.y !== undefined) section.y = data.y;
  
  return section;
} 
```

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

```typescript
/**
 * Universal element creator for Figma plugin
 * Acts as a central entry point for creating any type of Figma element
 */

import { 
  createRectangleFromData, 
  createEllipseFromData,
  createPolygonFromData,
  createStarFromData,
  createLineFromData,
  createVectorFromData
} from './shapeCreators';

import {
  createFrameFromData,
  createComponentFromData,
  createInstanceFromData,
  createGroupFromData,
  createSectionFromData
} from './containerCreators';

import { createTextFromData } from './textCreator';

import {
  createBooleanOperationFromData,
  createConnectorFromData,
  createShapeWithTextFromData,
  createCodeBlockFromData,
  createTableFromData,
  createWidgetFromData,
  createMediaFromData
} from './specialCreators';

import {
  createImageFromData,
  createImageFromBytesAsync,
  createGifFromData,
  createVideoFromDataAsync,
  createLinkPreviewFromDataAsync
} from './imageCreators';

import {
  createSliceFromData,
  createPageFromData,
  createPageDividerFromData,
  createSlideFromData,
  createSlideRowFromData
} from './sliceCreators';

import {
  createComponentFromNodeData,
  createComponentSetFromData
} from './componentCreators';

import { applyCommonProperties, selectAndFocusNodes } from '../utils/nodeUtils';

/**
 * Unified create element function that works with structured data
 * Detects the type of element to create based on the data.type property
 * 
 * @param data Configuration data with type and other properties
 * @returns Created Figma node or null if creation failed
 */
export async function createElementFromData(data: any): Promise<SceneNode | null> {
  if (!data || !data.type) {
    console.error('Invalid element data: missing type');
    return null;
  }
  
  let element: SceneNode | null = null;
  
  try {
    // Create the element based on its type
    switch (data.type.toLowerCase()) {
      // Basic shapes
      case 'rectangle':
        element = createRectangleFromData(data);
        break;
        
      case 'ellipse':
      case 'circle':
        element = createEllipseFromData(data);
        break;
        
      case 'polygon':
        element = createPolygonFromData(data);
        break;
        
      case 'star':
        element = createStarFromData(data);
        break;
        
      case 'line':
        element = createLineFromData(data);
        break;
        
      case 'vector':
        element = createVectorFromData(data);
        break;
      
      // Container elements
      case 'frame':
        element = createFrameFromData(data);
        break;
        
      case 'component':
        element = createComponentFromData(data);
        break;
        
      case 'componentfromnode':
        element = createComponentFromNodeData(data);
        break;
        
      case 'componentset':
        element = createComponentSetFromData(data);
        break;
        
      case 'instance':
        element = createInstanceFromData(data);
        break;
        
      case 'section':
        element = createSectionFromData(data);
        break;
        
      // Text
      case 'text':
        element = await createTextFromData(data);
        break;
        
      // Special types
      case 'boolean':
      case 'booleanoperation':
        element = createBooleanOperationFromData(data);
        break;
        
      case 'connector':
        element = createConnectorFromData(data);
        break;
        
      case 'shapewithtext':
        element = createShapeWithTextFromData(data);
        break;
        
      case 'codeblock':
        element = createCodeBlockFromData(data);
        break;
        
      case 'table':
        element = createTableFromData(data);
        break;
        
      case 'widget':
        element = createWidgetFromData(data);
        break;
        
      case 'media':
        element = createMediaFromData(data);
        break;
        
      // Image and media types
      case 'image':
        if (data.bytes || data.file) {
          element = await createImageFromBytesAsync(data);
        } else {
          element = createImageFromData(data);
        }
        break;
        
      case 'gif':
        element = createGifFromData(data);
        break;
        
      case 'video':
        element = await createVideoFromDataAsync(data);
        break;
        
      case 'linkpreview':
        element = await createLinkPreviewFromDataAsync(data);
        break;
        
      // Page and slice types
      case 'slice':
        element = createSliceFromData(data);
        break;
        
      case 'page':
        // PageNode is not a SceneNode in Figma's type system
        // So we create it but don't return it through the same path
        const page = createPageFromData(data);
        console.log(`Created page: ${page.name}`);
        // We return null as we can't return a PageNode as SceneNode
        return null;
        
      case 'pagedivider':
        element = createPageDividerFromData(data);
        break;
        
      case 'slide':
        element = createSlideFromData(data);
        break;
        
      case 'sliderow':
        element = createSlideRowFromData(data);
        break;
        
      // Special cases
      case 'group':
        if (!data.children || !Array.isArray(data.children) || data.children.length < 1) {
          console.error('Cannot create group: children array is required');
          return null;
        }
        
        // Create all child elements first
        const childNodes: SceneNode[] = [];
        for (const childData of data.children) {
          const child = await createElementFromData(childData);
          if (child) childNodes.push(child);
        }
        
        if (childNodes.length > 0) {
          element = createGroupFromData(data, childNodes);
        } else {
          console.error('Cannot create group: no valid children were created');
          return null;
        }
        break;
        
      default:
        console.error(`Unsupported element type: ${data.type}`);
        return null;
    }
    
    // Apply common properties if element was created
    if (element) {
      applyCommonProperties(element, data);
      
      // Select and focus on the element if requested
      if (data.select !== false) {
        selectAndFocusNodes(element);
      }
    }
    
    return element;
  } catch (error) {
    console.error(`Error creating element: ${error instanceof Error ? error.message : 'Unknown error'}`);
    return null;
  }
}

/**
 * Create multiple elements from an array of data objects
 * @param dataArray Array of element configuration data
 * @returns Array of created nodes
 */
export async function createElementsFromDataArray(dataArray: any[]): Promise<SceneNode[]> {
  const createdNodes: SceneNode[] = [];
  
  for (const data of dataArray) {
    const node = await createElementFromData(data);
    if (node) createdNodes.push(node);
  }
  
  // If there are created nodes, select them all at the end
  if (createdNodes.length > 0) {
    selectAndFocusNodes(createdNodes);
  }
  
  return createdNodes;
} 
```

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

```typescript
/**
 * Page tools for the Figma MCP server
 */
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import {
  isPluginConnected,
  sendCommandToPlugin,
} from "../services/websocket.js";

// Define interfaces for page data
interface PageData {
  id: string;
  name: string;
  children?: any[];
  [key: string]: any;
}

export const getPagesTool = (server: McpServer) => {
  server.tool("get_pages", {}, async () => {
    try {
      // Get pages using WebSocket only if plugin is connected
      if (!isPluginConnected()) {
        return {
          content: [
            {
              type: "text",
              text: "No Figma plugin is connected. Please make sure the Figma plugin is running and connected to the MCP server.",
            },
          ],
        };
      }

      const response = await sendCommandToPlugin("get-pages", {});

      if (!response.success) {
        throw new Error(response.error || "Failed to get pages");
      }

      // Process the response to handle different result formats
      const result = response.result || {};
      const pages = result.items || [];
      const pagesCount = result.count || 0;

      // Check if we have pages to display
      if (pagesCount === 0 && !pages.length) {
        return {
          content: [
            { type: "text", text: `# Pages in Figma File` },
            { type: "text", text: `No pages found in the current Figma file.` },
          ],
        };
      }

      return {
        content: [
          { type: "text", text: `# Pages in Figma File` },
          { type: "text", text: `Found ${pagesCount || pages.length} pages:` },
          {
            type: "text",
            text: pages
              .map((page: PageData) => `- ${page.name} (ID: ${page.id})`)
              .join("\n"),
          },
        ],
      };
    } catch (error) {
      console.error("Error fetching pages:", error);
      return {
        content: [
          {
            type: "text",
            text: `Error getting pages: ${(error as Error).message}`,
          },
        ],
      };
    }
  });
};

export const getPageTool = (server: McpServer) => {
  server.tool(
    "get_page",
    {
      page_id: z.string().min(1).describe("The ID of the page to retrieve").optional(),
    },
    async ({ page_id }) => {
      try {
        // Get page using WebSocket only if plugin is connected
        if (!isPluginConnected()) {
          return {
            content: [
              {
                type: "text",
                text: "No Figma plugin is connected. Please make sure the Figma plugin is running and connected to the MCP server.",
              },
            ],
          };
        }

        // If page_id is not provided, get the current page
        const response = await sendCommandToPlugin("get-page", {
          page_id,
        });

        if (!response.success) {
          throw new Error(response.error || "Failed to get page");
        }

        const pageNode = response.result;

        return {
          content: [
            { type: "text", text: `# Page: ${pageNode.name}` },
            { type: "text", text: `ID: ${pageNode.id}` },
            { type: "text", text: `Type: ${pageNode.type}` },
            {
              type: "text",
              text: `Elements: ${pageNode.children?.length || 0}`,
            },
            {
              type: "text",
              text: "```json\n" + JSON.stringify(pageNode, null, 2) + "\n```",
            },
          ],
        };
      } catch (error) {
        console.error("Error fetching page:", error);
        return {
          content: [
            {
              type: "text",
              text: `Error getting page: ${(error as Error).message}`,
            },
          ],
        };
      }
    }
  );
};

export const createPageTool = (server: McpServer) => {
  server.tool(
    "create_page",
    {
      page_name: z.string().min(1).describe("Name for the new page"),
    },
    async ({ page_name }) => {
      try {
        if (!isPluginConnected()) {
          return {
            content: [
              {
                type: "text",
                text: "No Figma plugin is connected. Please make sure the Figma plugin is running and connected to the MCP server.",
              },
            ],
          };
        }

        // Use WebSocket to send command to plugin
        const response = await sendCommandToPlugin("create-page", {
          name: page_name,
        });

        if (!response.success) {
          throw new Error(response.error || "Failed to create page");
        }

        return {
          content: [
            {
              type: "text",
              text: `# Page Created Successfully`,
            },
            {
              type: "text",
              text: `A new page named "${page_name}" has been created.`,
            },
            {
              type: "text",
              text:
                response.result && response.result.id
                  ? `Page ID: ${response.result.id}`
                  : `Creation successful`,
            },
          ],
        };
      } catch (error) {
        console.error("Error creating page:", error);
        return {
          content: [
            {
              type: "text",
              text: `Error creating page: ${(error as Error).message}`,
            },
          ],
        };
      }
    }
  );
};

export const switchPageTool = (server: McpServer) => {
  server.tool(
    "switch_page",
    {
      page_id: z.string().min(1).describe("The ID of the page to switch to"),
    },
    async ({ page_id }) => {
      try {
        if (!isPluginConnected()) {
          return {
            content: [
              {
                type: "text",
                text: "No Figma plugin is connected. Please make sure the Figma plugin is running and connected to the MCP server.",
              },
            ],
          };
        }

        // Use WebSocket to send command to plugin
        const response = await sendCommandToPlugin("switch-page", {
          id: page_id, // Note: plugin expects 'id', not 'page_id'
        });

        if (!response.success) {
          throw new Error(response.error || "Failed to switch page");
        }

        return {
          content: [
            {
              type: "text",
              text: `# Page Switched Successfully`,
            },
            {
              type: "text",
              text: `Successfully switched to page with ID: ${page_id}`,
            },
            {
              type: "text",
              text:
                response.result && response.result.name
                  ? `Current page: ${response.result.name}`
                  : `Switch successful`,
            },
          ],
        };
      } catch (error) {
        console.error("Error switching page:", error);
        return {
          content: [
            {
              type: "text",
              text: `Error switching page: ${(error as Error).message}`,
            },
          ],
        };
      }
    }
  );
};

/**
 * Registers all page-related tools with the MCP server
 */
export const registerPageTools = (server: McpServer): void => {
  getPagesTool(server);
  getPageTool(server);
  createPageTool(server);
  switchPageTool(server);
};

```

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

```typescript
/**
 * Text element creation functions for Figma plugin
 */

import { createSolidPaint } from '../utils/colorUtils';
import { selectAndFocusNodes } from '../utils/nodeUtils';

/**
 * Create a text node from data
 * @param data Text configuration data
 * @returns Created text node
 */
export async function createTextFromData(data: any): Promise<TextNode> {
  const text = figma.createText();
  
  // Load font - default to Inter if not specified
  const fontFamily = data.fontFamily || (data.fontName.family) || "Inter";
  const fontStyle = data.fontStyle || (data.fontName.style) || "Regular";
  
  // Load the font before setting text
  try {
    await figma.loadFontAsync({ family: fontFamily, style: fontStyle });
  } catch (error) {
    console.warn(`Failed to load font ${fontFamily} ${fontStyle}. Falling back to Inter Regular.`);
    await figma.loadFontAsync({ family: "Inter", style: "Regular" });
  }
  
  // Set basic text content
  text.characters = data.text || data.characters || "Text";
  
  // Position and size
  if (data.x !== undefined) text.x = data.x;
  if (data.y !== undefined) text.y = data.y;
  
  // Text size and dimensions
  if (data.fontSize) text.fontSize = data.fontSize;
  if (data.width) text.resize(data.width, text.height);
  
  // Text style and alignment
  if (data.fontName) text.fontName = data.fontName;
  if (data.textAlignHorizontal) text.textAlignHorizontal = data.textAlignHorizontal;
  if (data.textAlignVertical) text.textAlignVertical = data.textAlignVertical;
  if (data.textAutoResize) text.textAutoResize = data.textAutoResize;
  if (data.textTruncation) text.textTruncation = data.textTruncation;
  if (data.maxLines !== undefined) text.maxLines = data.maxLines;
  
  // Paragraph styling
  if (data.paragraphIndent) text.paragraphIndent = data.paragraphIndent;
  if (data.paragraphSpacing) text.paragraphSpacing = data.paragraphSpacing;
  if (data.listSpacing) text.listSpacing = data.listSpacing;
  if (data.hangingPunctuation !== undefined) text.hangingPunctuation = data.hangingPunctuation;
  if (data.hangingList !== undefined) text.hangingList = data.hangingList;
  if (data.autoRename !== undefined) text.autoRename = data.autoRename;
  
  // Text styling
  if (data.letterSpacing) text.letterSpacing = data.letterSpacing;
  if (data.lineHeight) text.lineHeight = data.lineHeight;
  if (data.leadingTrim) text.leadingTrim = data.leadingTrim;
  if (data.textCase) text.textCase = data.textCase;
  if (data.textDecoration) text.textDecoration = data.textDecoration;
  if (data.textStyleId) text.textStyleId = data.textStyleId;
  
  // Text decoration details
  if (data.textDecorationStyle) text.textDecorationStyle = data.textDecorationStyle;
  if (data.textDecorationOffset) text.textDecorationOffset = data.textDecorationOffset;
  if (data.textDecorationThickness) text.textDecorationThickness = data.textDecorationThickness;
  if (data.textDecorationColor) text.textDecorationColor = data.textDecorationColor;
  if (data.textDecorationSkipInk !== undefined) text.textDecorationSkipInk = data.textDecorationSkipInk;
  
  // Text fill
  if (data.fills) {
    text.fills = data.fills;
  } else if (data.fill) {
    if (typeof data.fill === 'string') {
      text.fills = [createSolidPaint(data.fill)];
    } else {
      text.fills = [data.fill];
    }
  }
  
  // Text hyperlink
  if (data.hyperlink) {
    text.hyperlink = data.hyperlink;
  }
  
  // Layout properties
  if (data.layoutAlign) text.layoutAlign = data.layoutAlign;
  if (data.layoutGrow !== undefined) text.layoutGrow = data.layoutGrow;
  if (data.layoutSizingHorizontal) text.layoutSizingHorizontal = data.layoutSizingHorizontal;
  if (data.layoutSizingVertical) text.layoutSizingVertical = data.layoutSizingVertical;
  
  // Apply text range styles if provided
  if (data.rangeStyles && Array.isArray(data.rangeStyles)) {
    applyTextRangeStyles(text, data.rangeStyles);
  }
  
  // Apply common base properties
  if (data.name) text.name = data.name;
  if (data.visible !== undefined) text.visible = data.visible;
  if (data.locked !== undefined) text.locked = data.locked;
  if (data.opacity !== undefined) text.opacity = data.opacity;
  if (data.blendMode) text.blendMode = data.blendMode;
  if (data.effects) text.effects = data.effects;
  if (data.effectStyleId) text.effectStyleId = data.effectStyleId;
  if (data.exportSettings) text.exportSettings = data.exportSettings;
  if (data.constraints) text.constraints = data.constraints;
  
  return text;
}

/**
 * Create a simple text node with basic properties
 * @param x X coordinate
 * @param y Y coordinate
 * @param content Text content
 * @param fontSize Font size
 * @returns Created text node
 */
export async function createText(x: number, y: number, content: string, fontSize: number): Promise<TextNode> {
  // Use the data-driven function
  const text = await createTextFromData({
    text: content,
    fontSize,
    x,
    y
  });
  
  // Select and focus
  selectAndFocusNodes(text);
  
  return text;
}

/**
 * Apply character-level styling to text ranges in a text node
 * @param textNode Text node to style
 * @param ranges Array of range objects with start, end, and style properties
 */
export function applyTextRangeStyles(textNode: TextNode, ranges: Array<{start: number, end: number, style: any}>): void {
  for (const range of ranges) {
    // Apply individual style properties to the range
    for (const [property, value] of Object.entries(range.style)) {
      if (property === 'fills') {
        textNode.setRangeFills(range.start, range.end, value as Paint[]);
      } else if (property === 'fillStyleId') {
        textNode.setRangeFillStyleId(range.start, range.end, value as string);
      } else if (property === 'fontName') {
        textNode.setRangeFontName(range.start, range.end, value as FontName);
      } else if (property === 'fontSize') {
        textNode.setRangeFontSize(range.start, range.end, value as number);
      } else if (property === 'textCase') {
        textNode.setRangeTextCase(range.start, range.end, value as TextCase);
      } else if (property === 'textDecoration') {
        textNode.setRangeTextDecoration(range.start, range.end, value as TextDecoration);
      } else if (property === 'textDecorationStyle') {
        textNode.setRangeTextDecorationStyle(range.start, range.end, value as TextDecorationStyle);
      } else if (property === 'textDecorationOffset') {
        textNode.setRangeTextDecorationOffset(range.start, range.end, value as TextDecorationOffset);
      } else if (property === 'textDecorationThickness') {
        textNode.setRangeTextDecorationThickness(range.start, range.end, value as TextDecorationThickness);
      } else if (property === 'textDecorationColor') {
        textNode.setRangeTextDecorationColor(range.start, range.end, value as TextDecorationColor);
      } else if (property === 'textDecorationSkipInk') {
        textNode.setRangeTextDecorationSkipInk(range.start, range.end, value as boolean);
      } else if (property === 'letterSpacing') {
        textNode.setRangeLetterSpacing(range.start, range.end, value as LetterSpacing);
      } else if (property === 'lineHeight') {
        textNode.setRangeLineHeight(range.start, range.end, value as LineHeight);
      } else if (property === 'hyperlink') {
        textNode.setRangeHyperlink(range.start, range.end, value as HyperlinkTarget);
      } else if (property === 'textStyleId') {
        textNode.setRangeTextStyleId(range.start, range.end, value as string);
      } else if (property === 'indentation') {
        textNode.setRangeIndentation(range.start, range.end, value as number);
      } else if (property === 'paragraphIndent') {
        textNode.setRangeParagraphIndent(range.start, range.end, value as number);
      } else if (property === 'paragraphSpacing') {
        textNode.setRangeParagraphSpacing(range.start, range.end, value as number);
      } else if (property === 'listOptions') {
        textNode.setRangeListOptions(range.start, range.end, value as TextListOptions);
      } else if (property === 'listSpacing') {
        textNode.setRangeListSpacing(range.start, range.end, value as number);
      }
    }
  }
} 
```

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

```typescript
import axios from 'axios';
import { env } from '../config/env.js';
import type {
  GetFileResponse,
  GetFileNodesResponse,
  GetImageFillsResponse,
  GetImagesResponse,
  GetCommentsResponse, 
  GetFileVersionsResponse,
  GetTeamProjectsResponse,
  GetProjectFilesResponse,
  GetTeamComponentsResponse,
  GetFileComponentsResponse,
  GetComponentResponse,
  GetTeamComponentSetsResponse,
  GetFileComponentSetsResponse,
  GetComponentSetResponse,
  GetTeamStylesResponse,
  GetFileStylesResponse,
  GetStyleResponse,
  PostCommentRequestBody,
  PostCommentResponse,
} from "@figma/rest-api-spec";

const FIGMA_API_BASE_URL = 'https://api.figma.com/v1';

/**
 * Type definition for CreateFrameOptions
 */
export interface CreateFrameOptions {
  name: string;
  width: number;
  height: number;
  x?: number;
  y?: number;
  fills?: Array<{
    type: string; 
    color: { r: number; g: number; b: number }; 
    opacity: number;
  }>;
  pageId?: string;
}

/**
 * Type definition for the expected response when creating a frame
 */
export interface CreateFrameResponse {
  frame: {
    id: string;
    name: string;
  };
  success: boolean;
}

/**
 * Service for interacting with the Figma API
 */
export class FigmaApiService {
  private readonly headers: Record<string, string>;

  constructor(accessToken: string = env.FIGMA_PERSONAL_ACCESS_TOKEN) {
    this.headers = {
      'X-Figma-Token': accessToken,
    };
  }

  /**
   * Get file by key
   */
  async getFile(fileKey: string, params: { ids?: string; depth?: number; geometry?: string; plugin_data?: string; branch_data?: boolean } = {}): Promise<GetFileResponse> {
    const response = await axios.get(`${FIGMA_API_BASE_URL}/files/${fileKey}`, {
      headers: this.headers,
      params,
    });
    return response.data;
  }

  /**
   * Get file nodes by key and node IDs
   */
  async getFileNodes(fileKey: string, nodeIds: string[], params: { depth?: number; geometry?: string; plugin_data?: string } = {}): Promise<GetFileNodesResponse> {
    const response = await axios.get(`${FIGMA_API_BASE_URL}/files/${fileKey}/nodes`, {
      headers: this.headers,
      params: {
        ...params,
        ids: nodeIds.join(','),
      },
    });
    return response.data;
  }

  /**
   * Get images for file nodes
   */
  async getImages(fileKey: string, nodeIds: string[], params: { 
    scale?: number; 
    format?: string; 
    svg_include_id?: boolean;
    svg_include_node_id?: boolean;
    svg_simplify_stroke?: boolean;
    use_absolute_bounds?: boolean;
    version?: string;
  } = {}): Promise<GetImagesResponse> {
    const response = await axios.get(`${FIGMA_API_BASE_URL}/images/${fileKey}`, {
      headers: this.headers,
      params: {
        ...params,
        ids: nodeIds.join(','),
      },
    });
    return response.data;
  }

  /**
   * Get image fills for a file
   */
  async getImageFills(fileKey: string): Promise<GetImageFillsResponse> {
    const response = await axios.get(`${FIGMA_API_BASE_URL}/files/${fileKey}/images`, {
      headers: this.headers,
    });
    return response.data;
  }

  /**
   * Get comments for a file
   */
  async getComments(fileKey: string, params: { as_md?: boolean } = {}): Promise<GetCommentsResponse> {
    const response = await axios.get(`${FIGMA_API_BASE_URL}/files/${fileKey}/comments`, {
      headers: this.headers,
      params,
    });
    return response.data;
  }

  /**
   * Post a comment to a file
   */
  async postComment(fileKey: string, data: PostCommentRequestBody): Promise<PostCommentResponse> {
    const response = await axios.post(
      `${FIGMA_API_BASE_URL}/files/${fileKey}/comments`,
      data,
      { headers: this.headers }
    );
    return response.data;
  }

  /**
   * Create a new frame in a Figma file
   * Note: This uses the Figma Plugin API which requires appropriate permissions
   */
  async createFrame(fileKey: string, options: CreateFrameOptions): Promise<CreateFrameResponse> {
    // Build the frame creation request payload
    const payload = {
      node: {
        type: "FRAME",
        name: options.name,
        size: {
          width: options.width,
          height: options.height,
        },
        position: {
          x: options.x || 0,
          y: options.y || 0,
        },
        fills: options.fills || [],
      },
      pageId: options.pageId,
    };
    
    const response = await axios.post(
      `${FIGMA_API_BASE_URL}/files/${fileKey}/nodes`,
      payload,
      { headers: this.headers }
    );
    
    return {
      frame: {
        id: response.data.node.id,
        name: response.data.node.name,
      },
      success: true
    };
  }

  /**
   * Get file versions
   */
  async getFileVersions(fileKey: string): Promise<GetFileVersionsResponse> {
    const response = await axios.get(`${FIGMA_API_BASE_URL}/files/${fileKey}/versions`, {
      headers: this.headers,
    });
    return response.data;
  }

  /**
   * Get team projects
   */
  async getTeamProjects(teamId: string): Promise<GetTeamProjectsResponse> {
    const response = await axios.get(`${FIGMA_API_BASE_URL}/teams/${teamId}/projects`, {
      headers: this.headers,
    });
    return response.data;
  }

  /**
   * Get project files
   */
  async getProjectFiles(projectId: string, params: { branch_data?: boolean } = {}): Promise<GetProjectFilesResponse> {
    const response = await axios.get(`${FIGMA_API_BASE_URL}/projects/${projectId}/files`, {
      headers: this.headers,
      params,
    });
    return response.data;
  }

  /**
   * Get team components
   */
  async getTeamComponents(teamId: string, params: { page_size?: number; after?: number; before?: number } = {}): Promise<GetTeamComponentsResponse> {
    const response = await axios.get(`${FIGMA_API_BASE_URL}/teams/${teamId}/components`, {
      headers: this.headers,
      params,
    });
    return response.data;
  }

  /**
   * Get file components
   */
  async getFileComponents(fileKey: string): Promise<GetFileComponentsResponse> {
    const response = await axios.get(`${FIGMA_API_BASE_URL}/files/${fileKey}/components`, {
      headers: this.headers,
    });
    return response.data;
  }

  /**
   * Get component by key
   */
  async getComponent(key: string): Promise<GetComponentResponse> {
    const response = await axios.get(`${FIGMA_API_BASE_URL}/components/${key}`, {
      headers: this.headers,
    });
    return response.data;
  }

  /**
   * Get team component sets
   */
  async getTeamComponentSets(teamId: string, params: { page_size?: number; after?: number; before?: number } = {}): Promise<GetTeamComponentSetsResponse> {
    const response = await axios.get(`${FIGMA_API_BASE_URL}/teams/${teamId}/component_sets`, {
      headers: this.headers,
      params,
    });
    return response.data;
  }

  /**
   * Get file component sets
   */
  async getFileComponentSets(fileKey: string): Promise<GetFileComponentSetsResponse> {
    const response = await axios.get(`${FIGMA_API_BASE_URL}/files/${fileKey}/component_sets`, {
      headers: this.headers,
    });
    return response.data;
  }

  /**
   * Get component set by key
   */
  async getComponentSet(key: string): Promise<GetComponentSetResponse> {
    const response = await axios.get(`${FIGMA_API_BASE_URL}/component_sets/${key}`, {
      headers: this.headers,
    });
    return response.data;
  }

  /**
   * Get team styles
   */
  async getTeamStyles(teamId: string, params: { page_size?: number; after?: number; before?: number } = {}): Promise<GetTeamStylesResponse> {
    const response = await axios.get(`${FIGMA_API_BASE_URL}/teams/${teamId}/styles`, {
      headers: this.headers,
      params,
    });
    return response.data;
  }

  /**
   * Get file styles
   */
  async getFileStyles(fileKey: string): Promise<GetFileStylesResponse> {
    const response = await axios.get(`${FIGMA_API_BASE_URL}/files/${fileKey}/styles`, {
      headers: this.headers,
    });
    return response.data;
  }

  /**
   * Get style by key
   */
  async getStyle(key: string): Promise<GetStyleResponse> {
    const response = await axios.get(`${FIGMA_API_BASE_URL}/styles/${key}`, {
      headers: this.headers,
    });
    return response.data;
  }
}

export default new FigmaApiService();

```

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

```typescript
/**
 * Special element creation functions for Figma plugin
 * Handles more specialized node types like boolean operations, connectors, etc.
 */

import { createSolidPaint } from '../utils/colorUtils';
import { applyCommonProperties } from '../utils/nodeUtils';

/**
 * Create a boolean operation from data
 * @param data Boolean operation configuration data
 * @returns Created boolean operation node
 */
export function createBooleanOperationFromData(data: any): BooleanOperationNode | null {
  // Boolean operations require child nodes
  if (!data.children || !Array.isArray(data.children) || data.children.length < 2) {
    console.error('Boolean operation requires at least 2 child nodes');
    return null;
  }

  // First we need to create the child nodes and ensure they're on the page
  let childNodes: SceneNode[] = [];
  try {
    for (const childData of data.children) {
      const node = figma.createRectangle(); // Placeholder, would need actual createElement logic here
      // In actual use, you'll need to create the proper node type and apply properties
      childNodes.push(node);
    }

    // Now create the boolean operation with the child nodes
    const booleanOperation = figma.createBooleanOperation();
    
    // Set the operation type
    if (data.booleanOperation) {
      booleanOperation.booleanOperation = data.booleanOperation;
    }
    
    // Apply common properties
    applyCommonProperties(booleanOperation, data);
    
    return booleanOperation;
  } catch (error) {
    console.error('Failed to create boolean operation:', error);
    // Clean up any created nodes to avoid leaving orphans
    childNodes.forEach(node => node.remove());
    return null;
  }
}

/**
 * Create a connector node from data
 * @param data Connector configuration data
 * @returns Created connector node
 */
export function createConnectorFromData(data: any): ConnectorNode {
  const connector = figma.createConnector();
  
  // Set connector specific properties
  if (data.connectorStart) connector.connectorStart = data.connectorStart;
  if (data.connectorEnd) connector.connectorEnd = data.connectorEnd;
  if (data.connectorStartStrokeCap) connector.connectorStartStrokeCap = data.connectorStartStrokeCap;
  if (data.connectorEndStrokeCap) connector.connectorEndStrokeCap = data.connectorEndStrokeCap;
  if (data.connectorLineType) connector.connectorLineType = data.connectorLineType;
  
  // Set stroke properties
  if (data.strokes) connector.strokes = data.strokes;
  if (data.strokeWeight) connector.strokeWeight = data.strokeWeight;
  
  // Apply common properties
  applyCommonProperties(connector, data);
  
  return connector;
}

/**
 * Create a shape with text node (used in FigJam)
 * This function might not work in all Figma versions
 * 
 * @param data Shape with text configuration data
 * @returns Created shape with text node
 */
export function createShapeWithTextFromData(data: any): ShapeWithTextNode | null {
  // Check if this node type is supported
  if (!('createShapeWithText' in figma)) {
    console.error('ShapeWithText creation is not supported in this Figma version');
    return null;
  }
  
  try {
    const shapeWithText = figma.createShapeWithText();
    
    // Set shape specific properties
    if (data.shapeType) shapeWithText.shapeType = data.shapeType;
    
    // Text content
    if (data.text || data.characters) {
      shapeWithText.text.characters = data.text || data.characters;
    }
    
    // Text styling - these properties may not be directly accessible on all versions
    try {
      if (data.fontSize) shapeWithText.text.fontSize = data.fontSize;
      if (data.fontName) shapeWithText.text.fontName = data.fontName;
      // These properties may not exist directly on TextSublayerNode depending on Figma version
      if (data.textAlignHorizontal && 'textAlignHorizontal' in shapeWithText.text) {
        (shapeWithText.text as any).textAlignHorizontal = data.textAlignHorizontal;
      }
      if (data.textAlignVertical && 'textAlignVertical' in shapeWithText.text) {
        (shapeWithText.text as any).textAlignVertical = data.textAlignVertical;
      }
    } catch (e) {
      console.warn('Some text properties could not be set on ShapeWithText:', e);
    }
    
    // Fill and stroke
    if (data.fills) shapeWithText.fills = data.fills;
    if (data.strokes) shapeWithText.strokes = data.strokes;
    
    // Apply common properties
    applyCommonProperties(shapeWithText, data);
    
    return shapeWithText;
  } catch (error) {
    console.error('Failed to create shape with text:', error);
    return null;
  }
}

/**
 * Create a code block node
 * @param data Code block configuration data
 * @returns Created code block node
 */
export function createCodeBlockFromData(data: any): CodeBlockNode {
  const codeBlock = figma.createCodeBlock();
  
  // Code content
  if (data.code) codeBlock.code = data.code;
  if (data.codeLanguage) codeBlock.codeLanguage = data.codeLanguage;
  
  // Apply common properties
  applyCommonProperties(codeBlock, data);
  
  return codeBlock;
}

/**
 * Create a table node
 * @param data Table configuration data
 * @returns Created table node
 */
export function createTableFromData(data: any): TableNode {
  // Create table with specified rows and columns (defaults to 2x2)
  const table = figma.createTable(
    data.numRows || 2,
    data.numColumns || 2
  );
  
  // Applying table styling
  // Note: Some properties may not be directly available depending on Figma version
  if (data.fills && 'fills' in table) {
    (table as any).fills = data.fills;
  }
  
  // Process cell data if provided
  if (data.cells && Array.isArray(data.cells)) {
    for (const cellData of data.cells) {
      if (cellData.rowIndex !== undefined && cellData.columnIndex !== undefined) {
        try {
          // Different Figma versions may have different API for accessing cells
          let cell;
          if ('cellAt' in table) {
            cell = table.cellAt(cellData.rowIndex, cellData.columnIndex);
          } else if ('getCellAt' in table) {
            cell = (table as any).getCellAt(cellData.rowIndex, cellData.columnIndex);
          }
          
          if (cell) {
            // Apply cell properties
            if (cellData.text && cell.text) cell.text.characters = cellData.text;
            if (cellData.fills && 'fills' in cell) cell.fills = cellData.fills;
            if (cellData.rowSpan && 'rowSpan' in cell) cell.rowSpan = cellData.rowSpan;
            if (cellData.columnSpan && 'columnSpan' in cell) cell.columnSpan = cellData.columnSpan;
          }
        } catch (e) {
          console.warn(`Could not set properties for cell at ${cellData.rowIndex}, ${cellData.columnIndex}:`, e);
        }
      }
    }
  }
  
  // Apply common properties
  applyCommonProperties(table, data);
  
  return table;
}

/**
 * Create a widget node (if supported in current Figma version)
 * @param data Widget configuration data
 * @returns Created widget node or null
 */
export function createWidgetFromData(data: any): WidgetNode | null {
  // Check if widget creation is supported
  if (!('createWidget' in figma)) {
    console.error('Widget creation is not supported in this Figma version');
    return null;
  }
  
  // Widgets require a package ID
  if (!data.widgetId) {
    console.error('Widget creation requires a widgetId');
    return null;
  }
  
  try {
    // Using type assertion since createWidget may not be recognized by TypeScript
    const widget = (figma as any).createWidget(data.widgetId);
    
    // Set widget properties
    if (data.widgetData) widget.widgetData = JSON.stringify(data.widgetData);
    if (data.width && data.height && 'resize' in widget) widget.resize(data.width, data.height);
    
    // Apply common properties
    applyCommonProperties(widget, data);
    
    return widget;
  } catch (error) {
    console.error('Failed to create widget:', error);
    return null;
  }
}

/**
 * Create a media node (if supported in current Figma version)
 * @param data Media configuration data
 * @returns Created media node or null
 */
export function createMediaFromData(data: any): MediaNode | null {
  // Check if media creation is supported
  if (!('createMedia' in figma)) {
    console.error('Media creation is not supported in this Figma version');
    return null;
  }
  
  // Media requires a hash
  if (!data.hash) {
    console.error('Media creation requires a valid media hash');
    return null;
  }
  
  try {
    // Using type assertion since createMedia may not be recognized by TypeScript
    const media = (figma as any).createMedia(data.hash);
    
    // Apply common properties
    applyCommonProperties(media, data);
    
    return media;
  } catch (error) {
    console.error('Failed to create media:', error);
    return null;
  }
} 
```

--------------------------------------------------------------------------------
/src/plugin/code.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Figma MCP Plugin
 * Allows manipulating elements on canvas through MCP tools
 */

// Import modules
import {
  createElementFromData,
  createElementsFromDataArray,
} from "./creators/elementCreator";
import {
  createEllipseFromData,
  createLineFromData,
  createPolygonFromData,
  createRectangleFromData,
  createStarFromData,
  createVectorFromData
} from "./creators/shapeCreators";
import { createTextFromData } from "./creators/textCreator";
import { hexToRgb } from "./utils/colorUtils";
import { buildResultObject, selectAndFocusNodes } from "./utils/nodeUtils";

// Show plugin UI
figma.showUI(__html__, { width: 320, height: 500 });

// Log that the plugin has loaded
console.log("Figma MCP Plugin loaded");

// Element creator mapping
type ElementCreator = (params: any) => SceneNode | Promise<SceneNode>;

const elementCreators: Record<string, ElementCreator> = {
  "create-rectangle": createRectangleFromData,
  "create-circle": createEllipseFromData,
  "create-ellipse": createEllipseFromData,
  "create-polygon": createPolygonFromData,
  "create-line": createLineFromData,
  "create-text": createTextFromData,
  "create-star": createStarFromData,
  "create-vector": createVectorFromData,
  "create-arc": (params: any) => {
    const ellipse = createEllipseFromData(params);
    if (params.arcData || (params.startAngle !== undefined && params.endAngle !== undefined)) {
      ellipse.arcData = {
        startingAngle: params.startAngle || params.arcData.startingAngle || 0,
        endingAngle: params.endAngle || params.arcData.endingAngle || 360,
        innerRadius: params.innerRadius || params.arcData.innerRadius || 0
      };
    }
    return ellipse;
  }
};

// Generic element creation function
async function createElement(type: string, params: any): Promise<SceneNode | null> {
  console.log(`Creating ${type} with params:`, params);
  
  // Get the creator function
  const creator = elementCreators[type];
  if (!creator) {
    console.error(`Unknown element type: ${type}`);
    return null;
  }
  
  try {
    // Create the element (handle both synchronous and asynchronous creators)
    const element = await Promise.resolve(creator(params));
    
    // Set position if provided
    if (element && params) {
      if (params.x !== undefined) element.x = params.x;
      if (params.y !== undefined) element.y = params.y;
    }
    
    // Select and focus the element
    if (element) {
      selectAndFocusNodes(element);
    }
    
    return element;
  } catch (error) {
    console.error(`Error creating ${type}:`, error);
    return null;
  }
}

// Handle messages from UI
figma.ui.onmessage = async function (msg) {
  console.log("Received message from UI:", msg);

  // Handle different types of messages
  if (elementCreators[msg.type]) {
    // Element creation messages
    await createElement(msg.type, msg);
  } else if (msg.type === "create-element") {
    // Unified create element method
    console.log("Creating element with data:", msg.data);
    createElementFromData(msg.data);
  } else if (msg.type === "create-elements") {
    // Create multiple elements at once
    console.log("Creating multiple elements with data:", msg.data);
    createElementsFromDataArray(msg.data);
  } else if (msg.type === "mcp-command") {
    // Handle commands from MCP tool via UI
    console.log(
      "Received MCP command:",
      msg.command,
      "with params:",
      msg.params
    );
    handleMcpCommand(msg.command, msg.params);
  } else if (msg.type === "cancel") {
    console.log("Closing plugin");
    figma.closePlugin();
  } else {
    console.log("Unknown message type:", msg.type);
  }
};

// Handle MCP commands
async function handleMcpCommand(command: string, params: any) {
  let result:
    | SceneNode
    | PageNode
    | readonly SceneNode[]
    | readonly PageNode[]
    | null = null;

  try {
    // Convert command format from mcp (create_rectangle) to plugin (create-rectangle)
    const pluginCommand = command.replace(/_/g, '-');
    
    switch (pluginCommand) {
      case "create-rectangle":
      case "create-circle":
      case "create-polygon":
      case "create-line":
      case "create-arc":
      case "create-vector":
        console.log(`MCP command: Creating ${pluginCommand.substring(7)} with params:`, params);
        result = await createElement(pluginCommand, params);
        break;

      case "create-text":
        console.log("MCP command: Creating text with params:", params);
        result = await createElement(pluginCommand, params);
        break;

      case "create-element":
        console.log("MCP command: Creating element with params:", params);
        result = await createElementFromData(params);
        break;

      case "create-elements":
        console.log(
          "MCP command: Creating multiple elements with params:",
          params
        );
        result = await createElementsFromDataArray(params);
        break;

      case "get-selection":
        console.log("MCP command: Getting current selection");
        result = figma.currentPage.selection;
        break;

      case "get-elements":
        console.log("MCP command: Getting elements with params:", params);
        const page = params.page_id 
          ? (figma.getNodeById(params.page_id) as PageNode) 
          : figma.currentPage;
          
        if (!page || page.type !== "PAGE") {
          throw new Error("Invalid page ID or node is not a page");
        }
        
        const nodeType = params.type || "ALL";
        const limit = params.limit || 100;
        const includeHidden = params.include_hidden || false;
        
        if (nodeType === "ALL") {
          // Get all nodes, filtered by visibility if needed
          result = includeHidden 
            ? page.children.slice(0, limit) 
            : page.children.filter(node => node.visible).slice(0, limit);
        } else {
          // Filter by node type and visibility
          result = page.findAll(node => {
            const typeMatch = node.type === nodeType;
            const visibilityMatch = includeHidden || node.visible;
            return typeMatch && visibilityMatch;
          }).slice(0, limit);
        }
        break;
        
      case "get-element":
        console.log("MCP command: Getting element with ID:", params.node_id);
        const node = figma.getNodeById(params.node_id);
        
        if (!node) {
          throw new Error("Element not found with ID: " + params.node_id);
        }
        
        // Check if the node is a valid type for our result
        if (!['DOCUMENT', 'PAGE'].includes(node.type)) {
          // For scene nodes with children, include children if requested
          if (params.include_children && 'children' in node) {
            result = [node as SceneNode, ...((node as any).children || [])];
          } else {
            result = node as SceneNode;
          }
        } else if (node.type === 'PAGE') {
          // Handle page nodes specially
          result = node as PageNode;
        } else {
          // For document or other unsupported node types
          throw new Error("Unsupported node type: " + node.type);
        }
        break;

      case "get-pages":
        console.log("MCP command: Getting all pages");
        result = figma.root.children;
        break;

      case "get-page":
        console.log("MCP command: Getting page with ID:", params.page_id);
        if (!params.page_id) {
          // If no page_id is provided, use the current page
          console.log("No page_id provided, using current page");
          result = figma.currentPage;
        } else {
          // If page_id is provided, find the page by ID
          const pageNode = figma.getNodeById(params.page_id);
          if (!pageNode || pageNode.type !== "PAGE")
            throw new Error("Invalid page ID or node is not a page");
          result = pageNode;
        }
        break;

      case "create-page":
        console.log("MCP command: Creating new page with name:", params.name);
        const newPage = figma.createPage();
        newPage.name = params.name || "New Page";
        result = newPage;
        break;

      case "switch-page":
        console.log("MCP command: Switching to page with ID:", params.id);
        if (!params.id) throw new Error("Page ID is required");
        const switchPageNode = figma.getNodeById(params.id);
        if (!switchPageNode || switchPageNode.type !== "PAGE")
          throw new Error("Invalid page ID");

        figma.currentPage = switchPageNode as PageNode;
        result = switchPageNode;
        break;

      case "modify-rectangle":
        console.log("MCP command: Modifying rectangle with ID:", params.id);
        if (!params.id) throw new Error("Rectangle ID is required");
        const modifyNode = figma.getNodeById(params.id);
        if (!modifyNode || modifyNode.type !== "RECTANGLE")
          throw new Error("Invalid rectangle ID");

        const rect = modifyNode as RectangleNode;
        if (params.x !== undefined) rect.x = params.x;
        if (params.y !== undefined) rect.y = params.y;
        if (params.width !== undefined && params.height !== undefined)
          rect.resize(params.width, params.height);
        if (params.cornerRadius !== undefined)
          rect.cornerRadius = params.cornerRadius;
        if (params.color)
          rect.fills = [{ type: "SOLID", color: hexToRgb(params.color) }];

        result = rect;
        break;

      default:
        console.log("Unknown MCP command:", command);
        throw new Error("Unknown command: " + command);
    }

    // Convert PageNode to a compatible format for buildResultObject if needed
    let resultForBuilder: SceneNode | readonly SceneNode[] | null = null;

    if (result === null) {
      resultForBuilder = null;
    } else if (Array.isArray(result)) {
      // For arrays, we rely on duck typing - both PageNode[] and SceneNode[] have id, name, type
      resultForBuilder = result as unknown as readonly SceneNode[];
    } else if ("type" in result && result.type === "PAGE") {
      // For individual PageNode, we rely on duck typing - PageNode has id, name, type like SceneNode
      resultForBuilder = result as unknown as SceneNode;
    } else {
      resultForBuilder = result as SceneNode;
    }

    // Build result object, avoiding possible null values
    const resultObject = buildResultObject(resultForBuilder);
    console.log("Command result:", resultObject);

    // Send success response to UI
    figma.ui.postMessage({
      type: "mcp-response",
      success: true,
      command: command,
      result: resultObject,
    });
    console.log("Response sent to UI");

    return resultObject;
  } catch (error) {
    console.error("Error handling MCP command:", error);

    // Send error response to UI
    figma.ui.postMessage({
      type: "mcp-response",
      success: false,
      command: command,
      error: error instanceof Error ? error.message : "Unknown error",
    });
    console.log("Error response sent to UI");

    throw error;
  }
}

```

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

```typescript
/**
 * Widget Tools - MCP server tools for interacting with Figma widgets
 */
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import figmaApi from "../../services/figma-api.js";
import { FigmaUtils } from "../../utils/figma-utils.js";

/**
 * Register widget-related tools with the MCP server
 * @param server The MCP server instance
 */
export function registerWidgetTools(server: McpServer) {
  // Get all widget nodes in a file
  server.tool(
    "get_widgets",
    {
      file_key: z.string().min(1).describe("The Figma file key to retrieve widgets from")
    },
    async ({ file_key }) => {
      try {
        const file = await figmaApi.getFile(file_key);
        
        // Find all widget nodes in the file
        const widgetNodes = FigmaUtils.getNodesByType(file, 'WIDGET');
        
        if (widgetNodes.length === 0) {
          return {
            content: [
              { type: "text", text: `No widgets found in file ${file_key}` }
            ]
          };
        }
        
        const widgetsList = widgetNodes.map((node, index) => {
          const widgetSyncData = node.widgetSync ? 
            `\n   - Widget Sync Data: Available` : 
            `\n   - Widget Sync Data: None`;
            
          return `${index + 1}. **${node.name}** (ID: ${node.id})
   - Widget ID: ${node.widgetId || 'Unknown'}${widgetSyncData}`;
        }).join('\n\n');
        
        return {
          content: [
            { type: "text", text: `# Widgets in file ${file_key}` },
            { type: "text", text: `Found ${widgetNodes.length} widgets:` },
            { type: "text", text: widgetsList }
          ]
        };
      } catch (error) {
        console.error('Error fetching widgets:', error);
        return {
          content: [
            { type: "text", text: `Error getting widgets: ${(error as Error).message}` }
          ]
        };
      }
    }
  );

  // Get a specific widget node
  server.tool(
    "get_widget",
    {
      file_key: z.string().min(1).describe("The Figma file key"),
      node_id: z.string().min(1).describe("The ID of the widget node")
    },
    async ({ file_key, node_id }) => {
      try {
        const fileNodes = await figmaApi.getFileNodes(file_key, [node_id]);
        const nodeData = fileNodes.nodes[node_id];
        
        if (!nodeData || nodeData.document.type !== 'WIDGET') {
          return {
            content: [
              { type: "text", text: `Node ${node_id} not found in file ${file_key} or is not a widget` }
            ]
          };
        }
        
        const widgetNode = nodeData.document;
        
        // Get the sync data if available
        let syncDataContent = '';
        if (widgetNode.widgetSync) {
          try {
            const syncData = JSON.parse(widgetNode.widgetSync);
            syncDataContent = `\n\n## Widget Sync Data\n\`\`\`json\n${JSON.stringify(syncData, null, 2)}\n\`\`\``;
          } catch (error) {
            syncDataContent = '\n\n## Widget Sync Data\nError parsing widget sync data';
          }
        }
        
        return {
          content: [
            { type: "text", text: `# Widget: ${widgetNode.name}` },
            { type: "text", text: `ID: ${widgetNode.id}` },
            { type: "text", text: `Widget ID: ${widgetNode.widgetId || 'Unknown'}` },
            { type: "text", text: `Has Sync Data: ${widgetNode.widgetSync ? 'Yes' : 'No'}${syncDataContent}` }
          ]
        };
      } catch (error) {
        console.error('Error fetching widget node:', error);
        return {
          content: [
            { type: "text", text: `Error getting widget: ${(error as Error).message}` }
          ]
        };
      }
    }
  );

  // Get widget sync data
  server.tool(
    "get_widget_sync_data",
    {
      file_key: z.string().min(1).describe("The Figma file key"),
      node_id: z.string().min(1).describe("The ID of the widget node")
    },
    async ({ file_key, node_id }) => {
      try {
        const fileNodes = await figmaApi.getFileNodes(file_key, [node_id]);
        const nodeData = fileNodes.nodes[node_id];
        
        if (!nodeData || nodeData.document.type !== 'WIDGET') {
          return {
            content: [
              { type: "text", text: `Node ${node_id} not found in file ${file_key} or is not a widget` }
            ]
          };
        }
        
        const widgetNode = nodeData.document;
        
        if (!widgetNode.widgetSync) {
          return {
            content: [
              { type: "text", text: `Widget ${node_id} does not have any sync data` }
            ]
          };
        }
        
        try {
          const syncData = JSON.parse(widgetNode.widgetSync);
          
          return {
            content: [
              { type: "text", text: `# Widget Sync Data for "${widgetNode.name}"` },
              { type: "text", text: `Widget ID: ${widgetNode.id}` },
              { type: "text", text: "```json\n" + JSON.stringify(syncData, null, 2) + "\n```" }
            ]
          };
        } catch (error) {
          console.error('Error parsing widget sync data:', error);
          return {
            content: [
              { type: "text", text: `Error parsing widget sync data: ${(error as Error).message}` }
            ]
          };
        }
      } catch (error) {
        console.error('Error fetching widget sync data:', error);
        return {
          content: [
            { type: "text", text: `Error getting widget sync data: ${(error as Error).message}` }
          ]
        };
      }
    }
  );

  // Search widgets by property values
  server.tool(
    "search_widgets",
    {
      file_key: z.string().min(1).describe("The Figma file key"),
      property_key: z.string().min(1).describe("The sync data property key to search for"),
      property_value: z.string().optional().describe("Optional property value to match (if not provided, returns all widgets with the property)")
    },
    async ({ file_key, property_key, property_value }) => {
      try {
        const file = await figmaApi.getFile(file_key);
        
        // Find all widget nodes
        const allWidgetNodes = FigmaUtils.getNodesByType(file, 'WIDGET');
        
        // Filter widgets that have the specified property
        const matchingWidgets = allWidgetNodes.filter(node => {
          if (!node.widgetSync) return false;
          
          try {
            const syncData = JSON.parse(node.widgetSync);
            
            // If property_value is provided, check for exact match
            if (property_value !== undefined) {
              // Handle different types of values (string, number, boolean)
              const propValue = syncData[property_key];
              
              if (typeof propValue === 'string') {
                return propValue === property_value;
              } else if (typeof propValue === 'number') {
                return propValue.toString() === property_value;
              } else if (typeof propValue === 'boolean') {
                return propValue.toString() === property_value;
              } else if (propValue !== null && typeof propValue === 'object') {
                return JSON.stringify(propValue) === property_value;
              }
              
              return false;
            }
            
            // If no value provided, just check if the property exists
            return property_key in syncData;
          } catch (error) {
            return false;
          }
        });
        
        if (matchingWidgets.length === 0) {
          return {
            content: [
              { type: "text", text: property_value ? 
                `No widgets found with property "${property_key}" = "${property_value}"` : 
                `No widgets found with property "${property_key}"` 
              }
            ]
          };
        }
        
        const widgetsList = matchingWidgets.map((node, index) => {
          let syncDataValue = '';
          try {
            const syncData = JSON.parse(node.widgetSync!);
            const value = syncData[property_key];
            syncDataValue = typeof value === 'object' ? 
              JSON.stringify(value) : 
              String(value);
          } catch (error) {
            syncDataValue = 'Error parsing sync data';
          }
          
          return `${index + 1}. **${node.name}** (ID: ${node.id})
   - Property "${property_key}": ${syncDataValue}`;
        }).join('\n\n');
        
        return {
          content: [
            { type: "text", text: property_value ? 
              `# Widgets with property "${property_key}" = "${property_value}"` : 
              `# Widgets with property "${property_key}"` 
            },
            { type: "text", text: `Found ${matchingWidgets.length} matching widgets:` },
            { type: "text", text: widgetsList }
          ]
        };
      } catch (error) {
        console.error('Error searching widgets:', error);
        return {
          content: [
            { type: "text", text: `Error searching widgets: ${(error as Error).message}` }
          ]
        };
      }
    }
  );

  // Get widget properties for modification
  server.tool(
    "analyze_widget_structure",
    {
      file_key: z.string().min(1).describe("The Figma file key"),
      node_id: z.string().min(1).describe("The ID of the widget node")
    },
    async ({ file_key, node_id }) => {
      try {
        const fileNodes = await figmaApi.getFileNodes(file_key, [node_id]);
        const nodeData = fileNodes.nodes[node_id];
        
        if (!nodeData || nodeData.document.type !== 'WIDGET') {
          return {
            content: [
              { type: "text", text: `Node ${node_id} not found in file ${file_key} or is not a widget` }
            ]
          };
        }
        
        const widgetNode = nodeData.document;
        
        // Create a full analysis of the widget
        const widgetAnalysis = {
          basic: {
            id: widgetNode.id,
            name: widgetNode.name,
            type: widgetNode.type,
            widgetId: widgetNode.widgetId || 'Unknown'
          },
          placement: {
            x: widgetNode.x || 0,
            y: widgetNode.y || 0,
            width: widgetNode.width || 0,
            height: widgetNode.height || 0,
            rotation: widgetNode.rotation || 0
          },
          syncData: null as any
        };
        
        // Parse the widget sync data if available
        if (widgetNode.widgetSync) {
          try {
            widgetAnalysis.syncData = JSON.parse(widgetNode.widgetSync);
          } catch (error) {
            widgetAnalysis.syncData = { error: 'Invalid sync data format' };
          }
        }
        
        return {
          content: [
            { type: "text", text: `# Widget Analysis: ${widgetNode.name}` },
            { type: "text", text: `## Basic Information` },
            { type: "text", text: "```json\n" + JSON.stringify(widgetAnalysis.basic, null, 2) + "\n```" },
            { type: "text", text: `## Placement` },
            { type: "text", text: "```json\n" + JSON.stringify(widgetAnalysis.placement, null, 2) + "\n```" },
            { type: "text", text: `## Sync Data` },
            { type: "text", text: widgetAnalysis.syncData ? 
              "```json\n" + JSON.stringify(widgetAnalysis.syncData, null, 2) + "\n```" : 
              "No sync data available" 
            }
          ]
        };
      } catch (error) {
        console.error('Error analyzing widget:', error);
        return {
          content: [
            { type: "text", text: `Error analyzing widget: ${(error as Error).message}` }
          ]
        };
      }
    }
  );
}

```

--------------------------------------------------------------------------------
/src/tools/frame.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Frame Tools - MCP server tools for working with Figma Frame components
 */
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import figmaApi from "../services/figma-api.js";
import { FigmaUtils } from "../utils/figma-utils.js";

/**
 * Register frame-related tools with the MCP server
 * @param server The MCP server instance
 */
export function registerFrameTools(server: McpServer) {
  // Get Frame component documentation
  server.tool("get_frame_documentation", {}, async () => {
    try {
      // Frame component documentation based on the provided text
      return {
        content: [
          { type: "text", text: "# Frame Component Documentation" },
          {
            type: "text",
            text: "Frame acts exactly like a non-autolayout Frame within Figma, where children are positioned using x and y constraints. This component is useful to define a layout hierarchy.",
          },
          {
            type: "text",
            text: "If you want to use autolayout, use AutoLayout instead.",
          },
          { type: "text", text: "## BaseProps" },
          {
            type: "text",
            text: "- **name**: string - The name of the component",
          },
          {
            type: "text",
            text: "- **hidden**: boolean - Toggles whether to show the component",
          },
          {
            type: "text",
            text: "- **onClick**: (event: WidgetClickEvent) => Promise<any> | void - Attach a click handler",
          },
          {
            type: "text",
            text: "- **key**: string | number - The key of the component",
          },
          {
            type: "text",
            text: "- **hoverStyle**: HoverStyle - The style to be applied when hovering",
          },
          {
            type: "text",
            text: "- **tooltip**: string - The tooltip shown when hovering",
          },
          {
            type: "text",
            text: "- **positioning**: 'auto' | 'absolute' - How to position the node inside an AutoLayout parent",
          },
          { type: "text", text: "## BlendProps" },
          {
            type: "text",
            text: "- **blendMode**: BlendMode - The blendMode of the component",
          },
          {
            type: "text",
            text: "- **opacity**: number - The opacity of the component",
          },
          {
            type: "text",
            text: "- **effect**: Effect | Effect[] - The effect of the component",
          },
          { type: "text", text: "## ConstraintProps" },
          {
            type: "text",
            text: "- **x**: number | HorizontalConstraint - The x position of the node",
          },
          {
            type: "text",
            text: "- **y**: number | VerticalConstraint - The y position of the node",
          },
          {
            type: "text",
            text: "- **overflow**: 'visible' | 'hidden' | 'scroll' - The overflow behavior",
          },
          { type: "text", text: "## SizeProps (Required)" },
          {
            type: "text",
            text: "- **width**: Size - The width of the component (required)",
          },
          {
            type: "text",
            text: "- **height**: Size - The height of the component (required)",
          },
          { type: "text", text: "- **minWidth**: number - The minimum width" },
          { type: "text", text: "- **maxWidth**: number - The maximum width" },
          {
            type: "text",
            text: "- **minHeight**: number - The minimum height",
          },
          {
            type: "text",
            text: "- **maxHeight**: number - The maximum height",
          },
          {
            type: "text",
            text: "- **rotation**: number - The rotation in degrees (-180 to 180)",
          },
          { type: "text", text: "## CornerProps" },
          {
            type: "text",
            text: "- **cornerRadius**: CornerRadius - The corner radius in pixels",
          },
          { type: "text", text: "## GeometryProps" },
          {
            type: "text",
            text: "- **fill**: HexCode | Color | Paint | (SolidPaint | GradientPaint)[] - The fill paints",
          },
          {
            type: "text",
            text: "- **stroke**: HexCode | Color | SolidPaint | GradientPaint | (SolidPaint | GradientPaint)[] - The stroke paints",
          },
          {
            type: "text",
            text: "- **strokeWidth**: number - The stroke thickness in pixels",
          },
          {
            type: "text",
            text: "- **strokeAlign**: StrokeAlign - The stroke alignment",
          },
          {
            type: "text",
            text: "- **strokeDashPattern**: number[] - The stroke dash pattern",
          },
        ],
      };
    } catch (error) {
      console.error("Error retrieving Frame documentation:", error);
      return {
        content: [
          {
            type: "text",
            text: `Error getting Frame documentation: ${
              (error as Error).message
            }`,
          },
        ],
      };
    }
  });

  // Create a new frame widget
  server.tool(
    "create_frame_widget",
    {
      name: z
        .string()
        .min(1)
        .describe("The name for the widget containing frames"),
      width: z.number().min(1).describe("The width of the frame"),
      height: z.number().min(1).describe("The height of the frame"),
      fill: z.string().optional().describe("The fill color (hex code)"),
    },
    async ({ name, width, height, fill }) => {
      try {
        // Create a sample widget code with Frame component
        const widgetCode = `
// ${name} - Figma Widget with Frame Component
const { widget } = figma;
const { Frame, Text } = widget;

function ${name.replace(/\\s+/g, "")}Widget() {
  return (
    <Frame
      name="${name}"
      width={${width}}
      height={${height}}
      fill={${fill ? `"${fill}"` : "[]"}}
      stroke="#E0E0E0"
      strokeWidth={1}
      cornerRadius={8}
    >
      <Text
        x={20}
        y={20}
        width={${width - 40}}
        horizontalAlignText="center"
        fill="#000000"
      >
        Frame Widget Example
      </Text>
    </Frame>
  );
}

widget.register(${name.replace(/\\s+/g, "")}Widget);
`;

        return {
          content: [
            { type: "text", text: `# Frame Widget Code` },
            {
              type: "text",
              text: `The following code creates a widget using the Frame component:`,
            },
            { type: "text", text: "```jsx\n" + widgetCode + "\n```" },
            { type: "text", text: `## Instructions` },
            { type: "text", text: `1. Create a new widget in Figma` },
            {
              type: "text",
              text: `2. Copy and paste this code into the widget code editor`,
            },
            {
              type: "text",
              text: `3. Customize the content inside the Frame as needed`,
            },
          ],
        };
      } catch (error) {
        console.error("Error generating frame widget code:", error);
        return {
          content: [
            {
              type: "text",
              text: `Error generating frame widget code: ${
                (error as Error).message
              }`,
            },
          ],
        };
      }
    }
  );

  // Create a frame directly in Figma file
  server.tool(
    "create_frame_in_figma",
    {
      file_key: z
        .string()
        .min(1)
        .describe("The Figma file key where the frame will be created"),
      page_id: z
        .string()
        .optional()
        .describe("The page ID where the frame will be created (optional)"),
      name: z
        .string()
        .min(1)
        .describe("The name for the new frame"),
      width: z.number().min(1).describe("The width of the frame in pixels"),
      height: z.number().min(1).describe("The height of the frame in pixels"),
      x: z.number().default(0).describe("The X position of the frame (default: 0)"),
      y: z.number().default(0).describe("The Y position of the frame (default: 0)"),
      fill_color: z.string().optional().describe("The fill color (hex code)"),
    },
    async ({ file_key, page_id, name, width, height, x, y, fill_color }) => {
      try {
        // Create a frame in Figma using the Figma API
        const createFrameResponse = await figmaApi.createFrame(file_key, {
          name,
          width,
          height, 
          x,
          y,
          fills: fill_color ? [{ type: "SOLID", color: hexToRgb(fill_color), opacity: 1 }] : [],
          pageId: page_id
        });
        
        return {
          content: [
            { type: "text", text: `# Frame Created Successfully` },
            { 
              type: "text", 
              text: `A new frame named "${name}" has been created in your Figma file.` 
            },
            {
              type: "text",
              text: `- Width: ${width}px\n- Height: ${height}px\n- Position: (${x}, ${y})`
            },
            {
              type: "text",
              text: `Frame ID: ${createFrameResponse.frame.id}`
            },
            { 
              type: "text", 
              text: `You can now view and edit this frame in your Figma file.` 
            }
          ],
        };
      } catch (error) {
        console.error("Error creating frame in Figma:", error);
        return {
          content: [
            {
              type: "text",
              text: `Error creating frame in Figma: ${
                (error as Error).message
              }`,
            },
            {
              type: "text",
              text: "Please make sure you have write access to the file and the file key is correct."
            }
          ],
        };
      }
    }
  );

  // Get all frames in a file
  server.tool(
    "get_frames",
    {
      file_key: z
        .string()
        .min(1)
        .describe("The Figma file key to retrieve frames from"),
    },
    async ({ file_key }) => {
      try {
        const file = await figmaApi.getFile(file_key);

        // Find all frame nodes in the file
        const frameNodes = FigmaUtils.getNodesByType(file, "FRAME");

        if (frameNodes.length === 0) {
          return {
            content: [
              { type: "text", text: `No frames found in file ${file_key}` },
            ],
          };
        }

        const framesList = frameNodes
          .map((node, index) => {
            // Add type assertion for frame nodes
            const frameNode = node as {
              id: string;
              name: string; 
              width?: number;
              height?: number;
              children?: Array<any>;
            };
            
            return `${index + 1}. **${frameNode.name}** (ID: ${frameNode.id})
   - Width: ${frameNode.width || "Unknown"}, Height: ${frameNode.height || "Unknown"}
   - Children: ${frameNode.children?.length || 0}`;
          })
          .join("\n\n");

        return {
          content: [
            { type: "text", text: `# Frames in file ${file_key}` },
            { type: "text", text: `Found ${frameNodes.length} frames:` },
            { type: "text", text: framesList },
          ],
        };
      } catch (error) {
        console.error("Error fetching frames:", error);
        return {
          content: [
            {
              type: "text",
              text: `Error getting frames: ${(error as Error).message}`,
            },
          ],
        };
      }
    }
  );
}

/**
 * Convert hex color code to RGB values
 * @param hex Hex color code (e.g., #RRGGBB or #RGB)
 * @returns RGB color object with r, g, b values between 0 and 1
 */
function hexToRgb(hex: string) {
  // Remove # if present
  hex = hex.replace(/^#/, '');
  
  // Parse hex values
  let r, g, b;
  if (hex.length === 3) {
    // Convert 3-digit hex to 6-digit
    r = parseInt(hex.charAt(0) + hex.charAt(0), 16) / 255;
    g = parseInt(hex.charAt(1) + hex.charAt(1), 16) / 255;
    b = parseInt(hex.charAt(2) + hex.charAt(2), 16) / 255;
  } else if (hex.length === 6) {
    r = parseInt(hex.substring(0, 2), 16) / 255;
    g = parseInt(hex.substring(2, 4), 16) / 255;
    b = parseInt(hex.substring(4, 6), 16) / 255;
  } else {
    throw new Error(`Invalid hex color: ${hex}`);
  }
  
  return { r, g, b };
}

```
Page 1/2FirstPrevNextLast