#
tokens: 15526/50000 37/37 files
lines: off (toggle) GitHub
raw markdown copy
# Directory Structure

```
├── .env.example
├── .gitignore
├── .vscode
│   └── settings.json
├── biome.jsonc
├── bun.build.script.ts
├── bun.lock
├── Dockerfile
├── docs
│   ├── banner.png
│   ├── bannerv3.png
│   ├── en
│   │   ├── FAQ.md
│   │   └── GettingStarted.md
│   ├── Info.md
│   └── zh
│       ├── FAQ.md
│       └── GettingStarted.md
├── glama.json
├── LICENSE
├── mcp.json
├── package.json
├── pnpm-lock.yaml
├── README-zh-CN.md
├── README.md
├── smithery.yaml
├── src
│   ├── cli.ts
│   ├── server
│   │   └── figma
│   │       ├── apis
│   │       │   ├── f2c.ts
│   │       │   └── figma.ts
│   │       ├── config.ts
│   │       ├── helpers
│   │       │   ├── downloader.ts
│   │       │   ├── figma.ts
│   │       │   └── index.ts
│   │       ├── index.ts
│   │       ├── notifications
│   │       │   ├── index.ts
│   │       │   └── logger.ts
│   │       ├── tools
│   │       │   ├── f2c.ts
│   │       │   ├── figma.ts
│   │       │   └── v03.ts
│   │       └── types
│   │           ├── f2c.ts
│   │           └── figma.ts
│   ├── stdio.ts
│   ├── streamable-http.ts
│   ├── test
│   │   ├── api.test.ts
│   │   └── e2e.test.ts
│   ├── transports
│   │   ├── stdio.ts
│   │   └── streamable-http
│   │       ├── http-server.ts
│   │       ├── index.ts
│   │       ├── with-session-steamable-http.ts
│   │       └── without-session-steamable-http.ts
│   └── utils
│       ├── fetch.ts
│       ├── index.ts
│       └── logger.ts
├── STARTKIT.md
├── tsconfig.json
└── user_rules.json
```

# Files

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

```
FIGMA_API_KEY=your_figma_api_key_here
```

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

```
node_modules
dist/
.env
.demo/
build/
.comate/
.kiro

```

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

```markdown
# F2C MCP Server 
![MCP Server](https://badge.mcpx.dev?type=server 'MCP Server')
[![smithery badge](https://smithery.ai/badge/@f2c-ai/f2c-mcp)](https://smithery.ai/server/@f2c-ai/f2c-mcp)
[![npm version][npm-version-src]][npm-version-href]
[![npm downloads][npm-downloads-src]][npm-downloads-href]
[![github][github-src]][github-href]
[![node][node-src]][node-href]


[npm-version-src]: https://img.shields.io/npm/v/@f2c/mcp?style=flat&colorA=18181B&colorB=F0DB4F
[npm-version-href]: https://npmjs.com/package/@f2c/mcp
[npm-downloads-src]: https://img.shields.io/npm/dm/@f2c/mcp?style=flat&colorA=18181B&colorB=F0DB4F
[npm-downloads-href]: https://npmjs.com/package/@f2c/mcp
[github-src]: https://img.shields.io/badge/github-@f2c/mcp-blue?style=flat&colorA=18181B&colorB=F0DB4F
[github-href]: https://github.com/f2c-ai/f2c-mcp
[node-src]: https://img.shields.io/node/v/@f2c/mcp?style=flat&colorA=18181B&colorB=F0DB4F
[node-href]: https://nodejs.org/en/about/previous-releases

> Due to [Figma REST API rate limits](https://developers.figma.com/docs/rest-api/rate-limits/), if you are affected, please switch to [@f2c/mcp-plugin](https://www.npmjs.com/package/@f2c/mcp-plugin) for normal operation.

English | [简体中文](./README-zh-CN.md)

A Model Context Protocol server for Figma Design to Code using [F2C](https://f2c.yy.com/).

<a href="https://glama.ai/mcp/servers/@f2c-ai/f2c-mcp">
  <img width="380" height="200" src="https://glama.ai/mcp/servers/@f2c-ai/f2c-mcp/badge" alt="f2c-mcp-server MCP server" />
</a>

## Features
<img alt="f2c" src="https://raw.githubusercontent.com/f2c-ai/f2c-mcp/main/docs/bannerv3.png" />

- 🎨 Pixel-Perfect HTML/CSS:F2C converts Figma designs to pixel-perfect HTML/CSS with precision.
- ⚛️ Multi-Framework Support:F2C generates React, CSS Modules, and Tailwind CSS code for fast development.
- 🧠 Figma Design Context:F2C integrates design context, ensuring compatibility with AI tools like Cursor.
- 🔗 Figma File URL Parsing:F2C converts design nodes via Figma URLs, streamlining workflows.
- 🖼️ Remote Image Localization:F2C automates downloading Figma images to local assets for efficiency.

## How it works
1. [Configure the Server](docs/en/GettingStarted.md) in an MCP-supported IDE (e.g., Cursor, Trae).
> recommended to use [Comate AI IDE](https://comate.baidu.com/zh/download/ai-ide) 
2. Open your chat in IDE (e.g. agent mode in Cursor).
3. Paste a link to a Figma Node (Right-click any node in the Figma Layer panel to copy it).
4. Enter your requirements in the chat, such as fetching node data, downloading images, converting to code, etc.

## Configuration and Development

See [Configuration and Development](docs/en/GettingStarted.md)

## Data Privacy Notice
The logging tools integrated in this project are used solely for basic usage statistics and error log reporting. No sensitive information or user data is collected. All reported data is used exclusively to improve product quality and user experience.

## FAQ
See [FAQ](docs/en/FAQ.md)

## Credits

Thanks to:

+ [Framelink Figma MCP Server](https://github.com/GLips/Figma-Context-MCP) Give Cursor and other AI-powered coding tools access to your Figma files with this Model Context Protocol server.  
+ [Cursor Talk to Figma MCP](https://github.com/sonnylazuardi/cursor-talk-to-figma-mcp) Allowing Cursor to communicate with Figma for reading designs and modifying them programmatically.
+ [Figma MCP Server](https://github.com/MatthewDailey/figma-mcp) This server provides tools for viewing, commenting, and analyzing Figma designs directly through the ModelContextProtocol.
```

--------------------------------------------------------------------------------
/src/server/figma/helpers/index.ts:
--------------------------------------------------------------------------------

```typescript
export * from './figma'
export * from './downloader'

```

--------------------------------------------------------------------------------
/glama.json:
--------------------------------------------------------------------------------

```json
{
  "$schema": "https://glama.ai/mcp/schemas/server.json",
  "maintainers": ["ckken"]
}

```

--------------------------------------------------------------------------------
/src/stdio.ts:
--------------------------------------------------------------------------------

```typescript
import {server} from 'src/server/figma'
import {startServer} from 'src/transports/stdio'
startServer(server)

```

--------------------------------------------------------------------------------
/src/streamable-http.ts:
--------------------------------------------------------------------------------

```typescript
import {startServer} from '@/transports/streamable-http'
import {server} from 'src/server/figma'
startServer(server, 3000)

```

--------------------------------------------------------------------------------
/src/cli.ts:
--------------------------------------------------------------------------------

```typescript
#!/usr/bin/env node

import {server} from 'src/server/figma'
import {startServer} from 'src/transports/stdio'
startServer(server)

```

--------------------------------------------------------------------------------
/src/server/figma/notifications/logger.ts:
--------------------------------------------------------------------------------

```typescript
import type {McpServer} from '@modelcontextprotocol/sdk/server/mcp.js'

export const registerLoggerNotificatons = (server: McpServer) => {}

```

--------------------------------------------------------------------------------
/mcp.json:
--------------------------------------------------------------------------------

```json
{
  "mcpServers": {
    "f2c-mcp": {
      "command": "npx",
      "args": ["-y", "f2c-mcp"],
      "env": {
        "personalToken": ""
      }
    }
  }
}

```

--------------------------------------------------------------------------------
/docs/en/FAQ.md:
--------------------------------------------------------------------------------

```markdown
# FAQ
 ```
Error: spawn npx ENOENT
```
Solutions: Add PATH to mcpServer
```
{
  "env": {
    "PATH": "/Users/xxx/.nvm/versions/node/v20.10.0/bin:/bin"
  }
}
```
```

--------------------------------------------------------------------------------
/src/server/figma/notifications/index.ts:
--------------------------------------------------------------------------------

```typescript
import type {McpServer} from '@modelcontextprotocol/sdk/server/mcp.js'
import {registerLoggerNotificatons} from './logger'

export const registerNotificatons = (server: McpServer) => {
  registerLoggerNotificatons(server)
}

```

--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
FROM oven/bun:latest

WORKDIR /app

# Copy package files
COPY package*.json ./

COPY bun.lock ./
# Install dependencies
RUN bun install

# Copy application code
COPY . .

# Build the application
RUN bun run build

# Command will be provided by smithery.yaml
CMD ["node", "dist/stdio.js"]

```

--------------------------------------------------------------------------------
/bun.build.script.ts:
--------------------------------------------------------------------------------

```typescript
const script = process.env.npm_lifecycle_script || ''
const isDev = script.includes('--watch')

export const result = await Bun.build({
  entrypoints: ['src/stdio.ts', 'src/cli.ts', 'src/streamable-http.ts'],
  outdir: 'dist',
  format: 'cjs',
  target: 'node',
  sourcemap: 'linked',
  minify: !isDev,
  env: isDev ? 'inline' : 'disable',
})

```

--------------------------------------------------------------------------------
/smithery.yaml:
--------------------------------------------------------------------------------

```yaml
# Smithery configuration file: https://smithery.ai/docs/build/project-config

startCommand:
  type: stdio
  configSchema:
    # JSON Schema defining the configuration options for the MCP.
    type: object
    properties: {}
  exampleConfig: {}
  commandFunction:
    # A JS function that produces the CLI command based on the given config to start the MCP on stdio.
    |-
    (config) => ({ command: 'node', args: ['./dist/stdio.js'] })

```

--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------

```json
{
  "biome.enabled": true,
  "eslint.enable": false,
  "prettier.enable": false,
  "editor.codeActionsOnSave": {
    "quickfix.biome": "explicit",
    "source.organizeImports.biome": "explicit"
  },
  "typescript.tsdk": "node_modules/typescript/lib",
  "typescript.enablePromptUseWorkspaceTsdk": true,
  "editor.defaultFormatter": "biomejs.biome",
  "[typescript]": {
    "editor.defaultFormatter": "biomejs.biome"
  },
  "[json]": {
    "editor.defaultFormatter": "biomejs.biome"
  }
}

```

--------------------------------------------------------------------------------
/src/server/figma/types/figma.ts:
--------------------------------------------------------------------------------

```typescript
export interface GetFileParams {
  fileKey: string
  ids?: string
  version?: string
  depth?: number
  geometry?: 'paths'
  plugin_data?: string
  branch_data?: boolean
  personalToken?: string
}

export interface GetImagesParams {
  fileKey: string
  ids: string
  scale?: number
  format?: 'jpg' | 'png' | 'svg' | 'pdf'
  svg_include_id?: boolean
  svg_simplify_stroke?: boolean
  use_absolute_bounds?: boolean
  version?: string
  personalToken?: string
}

export interface GetKeyParams {
  fileKey: string
  personalToken?: string
}

```

--------------------------------------------------------------------------------
/src/server/figma/types/f2c.ts:
--------------------------------------------------------------------------------

```typescript
export interface NodeToCodeWithF2C {
  personal_token: string
  format: string
  nodeIds: string
  fileKey: string
}
export interface NodeToCodeWithF2COptions {
  personalToken?: string
  localPath?: string
  format?: string
  ids: string
  fileKey: string
  imgFormat: 'png' | 'jpg' | 'svg'
  scaleSize: number
  ideInfo?: string
}

export interface NodeToCodeAllFiles {
  files: NodeToCodeFile[]
  images: {
    [key: string]: {id: string; name: string; fileExt: 'png' | 'jpg' | 'svg'; nodeType: string}
  }
}
export interface NodeToCodeFile {
  content: string
  path: string
}

```

--------------------------------------------------------------------------------
/user_rules.json:
--------------------------------------------------------------------------------

```json
{
  "outputRules": {
    "defaultOutputPath": ".demo",
    "fileTypes": {
      "html": {
        "path": ".demo",
        "extension": ".html"
      },
      "css": {
        "path": ".demo",
        "extension": ".css"
      },
      "assets": {
        "path": ".demo",
        "allowedTypes": ["png", "jpg", "svg", "gif"]
      }
    },
    "rules": {
      "createDirectory": true,
      "overwrite": true,
      "separateStyles": true,
      "assetHandling": "copy",
      "naming": {
        "pattern": "${filename}-${timestamp}",
        "lowercase": true
      }
    }
  }
}

```

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

```json
{
  "compilerOptions": {
    "baseUrl": "./",
    "paths": {
      "@/*": ["./src/*"]
    },
    "target": "ES2020",
    "types": ["bun-types"],
    "lib": ["ES2021", "DOM"],
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "resolveJsonModule": true,
    "allowJs": true,
    "checkJs": true,
    /* EMIT RULES */
    "outDir": "./dist",
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    "removeComments": true,
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"]
}

```

--------------------------------------------------------------------------------
/src/server/figma/config.ts:
--------------------------------------------------------------------------------

```typescript
import {getArgValue} from '@/utils/index'
import {LogLevel, createLogger} from '@/utils/logger'

const logger = createLogger('FigmaConfig', LogLevel.INFO)

class FigmaConfig {
  public serverName = 'F2C MCP'
  public serverVersion = process.env.FIGMA_VERSION || '0.0.1'
  private _personalToken = getArgValue('figma-api-key') || process.env.FIGMA_API_KEY || process.env.personalToken || ''
  public get personalToken() {
    return this._personalToken
  }
  public set personalToken(token: string) {
    this._personalToken = token
    logger.debug('personalToken', token)
  }
}

export default new FigmaConfig()

```

--------------------------------------------------------------------------------
/src/server/figma/index.ts:
--------------------------------------------------------------------------------

```typescript
import {McpServer} from '@modelcontextprotocol/sdk/server/mcp.js'
import config from 'src/server/figma/config'
import {registerNotificatons} from './notifications'
// import {registerF2cServer} from 'src/server/figma/tools/f2c'
// import {registerFigmaServer} from 'src/server/figma/tools/figma'
import {registerV03Server} from './tools/v03'

export const server = new McpServer(
  {
    name: config.serverName,
    version: config.serverVersion,
  },
  {
    capabilities: {
      logging: {},
    },
  },
)

registerNotificatons(server)
// registerFigmaServer(server)
// registerF2cServer(server)
registerV03Server(server)

```

--------------------------------------------------------------------------------
/src/transports/stdio.ts:
--------------------------------------------------------------------------------

```typescript
import type {McpServer} from '@modelcontextprotocol/sdk/server/mcp.js'
import {StdioServerTransport} from '@modelcontextprotocol/sdk/server/stdio.js'
import {createLogger} from 'src/utils/logger'

const logger = createLogger('StdioTransport')

export async function startServer(server: McpServer) {
  try {
    const transport = new StdioServerTransport()
    await server.connect(transport)
  } catch (e: any) {
    logger.info(
      JSON.stringify({
        jsonrpc: '2.0',
        id: null,
        error: {
          code: -32000,
          message: `Server startup failed: ${e.message}`,
        },
      }),
    )
    process.exit(1)
  }
}

```

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

```typescript
/**
 * Get the value of a specified parameter from command line arguments
 * Supports --param=value format
 * @param {string} paramName - Parameter name (without -- prefix)
 * @param {string|undefined} defaultValue - Default value, returned if parameter not found
 * @returns {string|undefined} Parameter value or default value
 */
export function getArgValue(paramName: string, defaultValue?: string): string | undefined {
  const args = process.argv
  const paramPrefix = `--${paramName}=`

  for (const arg of args) {
    // Check if argument starts with specified prefix
    if (arg.startsWith(paramPrefix)) {
      return arg.substring(paramPrefix.length)
    }
  }

  // If parameter not found, return default value
  return defaultValue
}

```

--------------------------------------------------------------------------------
/src/server/figma/helpers/figma.ts:
--------------------------------------------------------------------------------

```typescript
// Enhanced Figma URL parser supporting multiple formats
export function parseFigmaUrl(url: string) {
  try {
    const urlObj = new URL(url)
    const path = urlObj.pathname

    // Support both file/xxx and design/xxx formats
    const [, fileKey] = path.match(/(?:file|design)\/([^/]+)/) || []

    // Support node-id parameter and hash format
    const nodeIdMatch =
      urlObj.searchParams.get('node-id') || url.match(/node-id=([^&]+)/) || url.match(/#([^:]+:[^:]+)/)

    const nodeId = nodeIdMatch ? (Array.isArray(nodeIdMatch) ? nodeIdMatch[1] : nodeIdMatch) : ''

    if (!fileKey) {
      throw new Error('Invalid Figma link: fileKey not found')
    }

    return {
      fileKey,
      nodeId: nodeId || '',
    }
  } catch (error) {
    throw new Error('Invalid Figma link')
  }
}

```

--------------------------------------------------------------------------------
/src/transports/streamable-http/index.ts:
--------------------------------------------------------------------------------

```typescript
import {createLogger} from '@/utils/logger'
import type {McpServer} from '@modelcontextprotocol/sdk/server/mcp.js'
const logger = createLogger('StreamableHttp')
type ServerTypeIns = 'all' | 'session' | 'no_session'
export async function startServer(server: McpServer, port = 3000, serverType: ServerTypeIns = 'all') {
  switch (serverType) {
    // don't support hot reload
    case 'all': {
      const {startHttpServer} = await import('./http-server.js')
      return startHttpServer(port, server)
    }
    // don't support hot reload
    case 'session': {
      const {startServer: startWithSessionServer} = await import('./with-session-steamable-http.js')
      return startWithSessionServer(server, port)
    }
    // support hot reload and dev
    case 'no_session': {
      const {startServer: startWithoutSessionServer} = await import('./without-session-steamable-http.js')
      return startWithoutSessionServer(server, port)
    }
  }
}

```

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

```typescript
export enum LogLevel {
  DEBUG = 0,
  INFO = 1,
  WARN = 2,
  ERROR = 3,
}
// Determine if stdio or http mode by checking the run command
function detectTransportMode(): boolean {
  const args = process.argv.join(' ')
  // If command line includes streamable-http.js or streamable-http.ts, it's HTTP mode
  return args.includes('streamable-http.js') || args.includes('streamable-http.ts')
}
export const isHttp = detectTransportMode()
export class Logger {
  private context: string
  private level: LogLevel
  constructor(context: string, level: LogLevel = LogLevel.INFO) {
    this.context = context
    this.level = level
  }

  setLevel(level: LogLevel): void {
    this.level = level
  }
  log(...args: any[]): void {
    if (isHttp) {
      console.log(...args)
    } else {
      console.error(...args)
    }
  }
  logWarn(...args: any[]): void {
    if (isHttp) {
      console.warn(...args)
    } else {
      console.error(...args)
    }
  }
  debug(...args: any[]): void {
    if (this.level <= LogLevel.DEBUG) {
      this.log(`[DEBUG] [${this.context}]`, ...args) // 使用 console.error 而不是 console.log
    }
  }

  info(...args: any[]): void {
    if (this.level <= LogLevel.INFO) {
      this.log(`[INFO] [${this.context}]`, ...args)
    }
  }

  warn(...args: any[]): void {
    if (this.level <= LogLevel.WARN) {
      this.logWarn(`[WARN] [${this.context}]`, ...args)
    }
  }

  error(...args: any[]): void {
    if (this.level <= LogLevel.ERROR) {
      console.error(`[ERROR] [${this.context}]`, ...args)
    }
  }
}

// Create default logger instance
export const createLogger = (context: string, level: LogLevel = LogLevel.INFO) => new Logger(context, level)

```

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

```json
{
  "name": "@f2c/mcp",
  "version": "0.4.9",
  "description": "",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/f2c-ai/f2c-mcp",
    "directory": "."
  },
  "publishConfig": {
    "access": "public"
  },
  "keywords": ["f2c", "mcp"],
  "files": ["dist"],
  "main": "dist/stdio.js",
  "types": "dist/stdio.d.ts",
  "exports": {
    ".": {
      "import": {
        "types": "./dist/stdio.d.mts",
        "default": "./dist/stdio.mjs"
      },
      "require": {
        "types": "./dist/stdio.d.ts",
        "default": "./dist/stdio.js"
      }
    },
    "./streamable-http": {
      "import": {
        "types": "./dist/streamable-http.d.mts",
        "default": "./dist/streamable-http.mjs"
      },
      "require": {
        "types": "./dist/streamable-http.d.ts",
        "default": "./dist/streamable-http.js"
      }
    }
  },
  "bin": {
    "f2c-mcp": "dist/cli.js"
  },
  "scripts": {
    "build": "bun run bun.build.script.ts",
    "dev": "bun --watch run bun.build.script.ts",
    "http": "node ./dist/streamable-http.js",
    "http:dev": "bun --env-file=.env --watch run src/streamable-http.ts",
    "http:prod": "bun --env-file= run src/streamable-http.ts",
    "inspector": "npx @modelcontextprotocol/inspector node ./dist/stdio.js",
    "lint": "biome check . --fix",
    "test": "bun test src/test/api.test.ts",
    "e2e": "bun test src/test/e2e.test.ts"
  },
  "author": "ckken",
  "maintainers": ["ckken"],
  "license": "ISC",
  "dependencies": {
    "cors": "^2.8.5",
    "express": "^5.1.0",
    "node-fetch": "^3.3.2",
    "zod": "^3.22.4",
    "@f2c/data-reporter": "^0.0.20"
  },
  "devDependencies": {
    "@biomejs/biome": "^1.9.4",
    "@empjs/biome-config": "^0.7.2",
    "@modelcontextprotocol/sdk": "1.17.2",
    "@types/bun": "^1.2.13",
    "@types/cors": "^2.8.18",
    "@types/express": "^5.0.1",
    "@types/node": "^22.15.0",
    "@types/node-fetch": "^2.6.12",
    "typescript": "^5.8.3"
  },
  "engines": {
    "node": ">=16.0.0"
  }
}

```

--------------------------------------------------------------------------------
/src/server/figma/apis/figma.ts:
--------------------------------------------------------------------------------

```typescript
import type {GetFileParams, GetImagesParams, GetKeyParams} from '@/server/figma/types/figma'
import config from 'src/server/figma/config'
import compatFetch from 'src/utils/fetch'
import {createLogger} from 'src/utils/logger'

const logger = createLogger('FigmaRestApi')
class FigmaRestApi {
  protected figmaHost = `https://api.figma.com/v1`
  async files(o: GetFileParams) {
    let url: string
    if (o.ids) {
      url = this.opToUrl(`${this.figmaHost}/files/${o.fileKey}/nodes`, o)
    } else {
      url = this.opToUrl(`${this.figmaHost}/files/${o.fileKey}`, o)
    }

    return this.fetch(url)
  }
  async images(o: GetImagesParams) {
    const url = this.opToUrl(`${this.figmaHost}/images/${o.fileKey}`, o)
    return this.fetch(url)
  }
  // Returns download links for all images present in image fills
  async imageFills(o: GetKeyParams) {
    const url = this.opToUrl(`${this.figmaHost}/files/${o.fileKey}/images`, o)
    return this.fetch(url)
  }
  // Returns the metadata for the file referred to by :key
  async meta(o: GetKeyParams) {
    const url = this.opToUrl(`${this.figmaHost}/files/${o.fileKey}/meta`, o)
    return this.fetch(url)
  }
  async fetch(url: string, resType: 'json' | 'text' = 'json'): Promise<any> {
    try {
      const fetchOptions = {
        method: 'GET',
        headers: {
          'X-FIGMA-TOKEN': config.personalToken,
        },
      }
      const response = await compatFetch(url, fetchOptions)
      // logger.debug('response', url, JSON.stringify(fetchOptions))
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`)
      }
      const data = resType === 'text' ? await response.text() : await response.json()
      return data
    } catch (error) {
      logger.error('HTTP error', error)
      throw error
    }
  }
  private opToUrl(api: string, o: any = {}, filters = ['fileKey', 'personalToken']) {
    if (Object.keys(o).length === 0) {
      return api
    }
    if (o.personalToken) {
      config.personalToken = o.personalToken
    }
    const url: any = new URL(api)
    for (const [key, value] of Object.entries(o)) {
      if (!filters.includes(key)) url.searchParams.append(key, value)
    }
    return url.toString()
  }
}
export default new FigmaRestApi()

```

--------------------------------------------------------------------------------
/src/server/figma/apis/f2c.ts:
--------------------------------------------------------------------------------

```typescript
import config from 'src/server/figma/config'
import type {NodeToCodeAllFiles, NodeToCodeFile, NodeToCodeWithF2COptions} from 'src/server/figma/types/f2c'
import compatFetch from 'src/utils/fetch'
import {LogLevel, createLogger} from 'src/utils/logger'

const logger = createLogger('F2cApi', LogLevel.INFO)
class F2cApi {
  protected f2cHost = `https://f2c-figma-api.yy.com/api`
  //
  async nodeToCode(o: NodeToCodeWithF2COptions): Promise<NodeToCodeFile[]> {
    const op = {
      fileKey: o.fileKey,
      nodeIds: o.ids,
      personal_token: o.personalToken || config.personalToken,
      option: {
        cssFramework: 'inlinecss',
        imgFormat: o.imgFormat || 'png',
        scaleSize: o.scaleSize || 2,
      },
      format: 'files',
      // format: 'allFiles',
    }
    if (o.format === 'react-cssmodules') {
      op.option.cssFramework = 'cssmodules'
    } else if (o.format === 'react-tailwind') {
      op.option.cssFramework = 'tailwindcss'
    }
    const url = this.opToUrl(`${this.f2cHost}/nodes`, op)
    return this.fetch(url, 'json', o.ideInfo || 'other')
  }
  async fetch(url: string, resType: 'json' | 'text' = 'json', ideInfo: string): Promise<any> {
    logger.debug('fetch', url, config.personalToken)
    try {
      const fetchOptions = {
        method: 'GET',
        headers: {
          'F2c-Api-Platform': `mcp-${ideInfo}`,

        },
      }
      logger.debug('fetch', url)
      const response = await compatFetch(url, fetchOptions)
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`)
      }
      const data = resType === 'text' ? await response.text() : await response.json()
      return data
    } catch (error) {
      logger.error('HTTP error', error)
      throw error
    }
  }
  private opToUrl(api: string, o: any = {}) {
    if (Object.keys(o).length === 0) {
      return api
    }
    const url: any = new URL(api)
    for (const [key, value] of Object.entries(o)) {
      if (typeof value === 'object' && value !== null) {
        for (const [nestedKey, nestedValue] of Object.entries(value)) {
          url.searchParams.append(`${key}[${nestedKey}]`, nestedValue as string)
        }
      } else {
        url.searchParams.append(key, value as string)
      }
    }
    return url.toString()
  }
}
export default new F2cApi()

```

--------------------------------------------------------------------------------
/src/transports/streamable-http/with-session-steamable-http.ts:
--------------------------------------------------------------------------------

```typescript
import {randomUUID} from 'node:crypto'
import {createLogger} from '@/utils/logger'
import type {McpServer} from '@modelcontextprotocol/sdk/server/mcp.js'
import {StreamableHTTPServerTransport} from '@modelcontextprotocol/sdk/server/streamableHttp.js'
import {isInitializeRequest} from '@modelcontextprotocol/sdk/types.js'
import express from 'express'

const logger = createLogger('SessionStreamableHttp')

const app = express()
app.use(express.json())
export const startServer = (server: McpServer, port = 3000) => {
  const transports: {[sessionId: string]: StreamableHTTPServerTransport} = {}
  app.post('/mcp', async (req, res) => {
    // let acceptHeader = req.headers.accept as string
    // if (acceptHeader === '*/*') {
    //   acceptHeader = '*/*,application/json, text/event-stream'
    //   req.headers.accept = acceptHeader
    // }
    res.setHeader('Content-Type', 'application/json')
    const sessionId = req.headers['mcp-session-id'] as string | undefined
    let transport: StreamableHTTPServerTransport
    if (sessionId && transports[sessionId]) {
      transport = transports[sessionId]
    } else if (!sessionId && isInitializeRequest(req.body)) {
      transport = new StreamableHTTPServerTransport({
        sessionIdGenerator: () => randomUUID(),
        enableJsonResponse: true,
        onsessioninitialized: sessionId => {
          transports[sessionId] = transport
        },
      })
      transport.onclose = () => {
        if (transport.sessionId) {
          delete transports[transport.sessionId]
        }
      }
      await server.connect(transport)
    } else {
      res.status(400).json({
        jsonrpc: '2.0',
        error: {
          code: -32000,
          message: 'Bad Request: No valid session ID provided',
        },
        id: null,
      })
      return
    }
    await transport.handleRequest(req, res, req.body)
  })
  const handleSessionRequest = async (req: express.Request, res: express.Response) => {
    const sessionId = req.headers['mcp-session-id'] as string | undefined
    if (!sessionId || !transports[sessionId]) {
      res.status(400).send('Invalid or missing session ID')
      return
    }
    const transport = transports[sessionId]
    await transport.handleRequest(req, res)
  }
  app.get('/mcp', handleSessionRequest)
  app.delete('/mcp', handleSessionRequest)
  app.listen(port, () => {
    logger.info(`MCP Session-based Streamable HTTP Server listening on port ${port}`)
    logger.info(`Server address: http://localhost:${port}/mcp`)
  })
}

```

--------------------------------------------------------------------------------
/src/transports/streamable-http/without-session-steamable-http.ts:
--------------------------------------------------------------------------------

```typescript
import {createLogger} from '@/utils/logger'
import type {McpServer} from '@modelcontextprotocol/sdk/server/mcp.js'
import {StreamableHTTPServerTransport} from '@modelcontextprotocol/sdk/server/streamableHttp.js'
import express from 'express'

const logger = createLogger('StatelessStreamableHttp')

const app = express()
app.use(
  express.json({
    type: ['application/json', 'application/*+json', '*/*'], // 扩展支持的 Content-Type
  }),
)
const noAllowAcess = (req: any, res: any, next: any) => {
  return res.writeHead(405).end(
    JSON.stringify({
      jsonrpc: '2.0',
      error: {
        code: -32000,
        message: 'Method not allowed.',
      },
      id: null,
    }),
  )
}
// const polyfillRequest = (req: any, res: any) => {
//   // 设置响应头
//   res.setHeader('Content-Type', 'application/json')
//   res.setHeader('Connection', 'keep-alive')
//   res.setHeader('Keep-Alive', 'timeout=5')

//   // let acceptHeader = req.headers.accept as string
//   // if (acceptHeader === '*/*') {
//   //   acceptHeader = '*/*,application/json, text/event-stream'
//   //   req.headers.accept = acceptHeader
//   // }

//   // 确保请求的 Content-Type 存在
//   if (!req.headers['content-type']) {
//     req.headers['content-type'] = 'application/json'
//   }
// }
export const startServer = (server: McpServer, port = 3000) => {
  app.post('/mcp', async (req, res) => {
    res.setHeader('Content-Type', 'application/json')
    logger.info('Request body:', JSON.stringify(req.body))
    // polyfillRequest(req, res)

    try {
      const transport: StreamableHTTPServerTransport = new StreamableHTTPServerTransport({
        sessionIdGenerator: undefined,
        enableJsonResponse: true,
      })

      res.on('close', () => {
        transport.close()
        server.close()
      })

      await server.connect(transport)
      await transport.handleRequest(req, res, req.body)
    } catch (error: any) {
      logger.error('Error handling MCP request:', error)
      logger.error('Error stack:', error.stack)
      if (!res.headersSent) {
        res.status(500).json({
          jsonrpc: '2.0',
          error: {
            code: -32603,
            message: 'Internal server error',
            data: {
              errorMessage: error.message,
              errorName: error.name,
            },
          },
          id: req.body?.id || null,
        })
      }
    }
  })

  app.get('/mcp', noAllowAcess)
  app.delete('/mcp', noAllowAcess)

  app.listen(port, () => {
    logger.info(`MCP Stateless Streamable HTTP server started, listening on port ${port}`)
    logger.info(`Server address: http://localhost:${port}/mcp`)
  })
}

```

--------------------------------------------------------------------------------
/docs/en/GettingStarted.md:
--------------------------------------------------------------------------------

```markdown
# Getting Started
Usually, code editors and other AI clients use a configuration file to manage MCP servers.

You can add the following content to the configuration file to set up the `f2c-mcp` server.

> NOTE: You will need to create a Figma access token to use this server. Instructions on how to create a Figma API access token can be found [here](https://help.figma.com/hc/en-us/articles/8085703771159-Manage-personal-access-tokens).

## No-Installation MCP Configuration (stdio)

### MacOS / Linux
[![Install MCP Server](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/install-mcp?name=f2c-mcp&config=JTdCJTIyY29tbWFuZCUyMiUzQSUyMm5weCUyMC15JTIwJTQwZjJjJTJGbWNwJTIyJTJDJTIyZW52JTIyJTNBJTdCJTIycGVyc29uYWxUb2tlbiUyMiUzQSUyMiUyMiU3RCU3RA%3D%3D)
```json
{
  "mcpServers": {
    "f2c-mcp": {
      "command": "npx",
      "args": [
        "-y",
        "@f2c/mcp"
      ],
      "env": {
        "personalToken": ""
      }
    }
  }
}
```
or
```json
{
  "mcpServers": {
    "f2c-mcp": {
      "command": "npx",
      "args": [
        "-y",
        "@f2c/mcp",
        "--figma-api-key=YOUR-KEY"
      ],
    }
  }
}
```

### Windows (stdio)
[![Install MCP Server](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/install-mcp?name=f2c-mcp&config=JTdCJTIyY29tbWFuZCUyMiUzQSUyMmNtZCUyMCUyRmMlMjBucHglMjAteSUyMCU0MGYyYyUyRm1jcCUyMiUyQyUyMmVudiUyMiUzQSU3QiUyMnBlcnNvbmFsVG9rZW4lMjIlM0ElMjIlMjIlN0QlN0Q%3D)
```json
{
  "mcpServers": {
    "f2c-mcp": {
      "command": "cmd",
      "args": ["/c", "npx", "-y", "@f2c/mcp"],
      "env": {
        "personalToken": ""
      }
    }
  }
}
```
or 
```json
{
  "mcpServers": {
    "f2c-mcp": {
      "command": "cmd",
      "args": ["/c", "npx", "-y", "@f2c/mcp", "--figma-api-key=YOUR-KEY"],
    }
  }
}
```

## Global Installation MCP Configuration (stdio)
For cases where MCP client instability causes installation errors, we can use global installation and then configure it.

```bash
npm install -g @f2c/mcp
```
[![Install MCP Server](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/install-mcp?name=f2c-mcp&config=JTdCJTIyY29tbWFuZCUyMiUzQSUyMmYyYy1tY3AlMjAlMjIlMkMlMjJlbnYlMjIlM0ElN0IlMjJwZXJzb25hbFRva2VuJTIyJTNBJTIyJTIyJTdEJTdE)
```json
{
  "mcpServers": {
    "f2c-mcp": {
      "command": "f2c-mcp",
      "args": [],
      "env": {
        "personalToken": ""
      }
    }
  }
}
```

## Other Configuration Types

### Add Streamable HTTP
[![Install MCP Server](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/install-mcp?name=f2c_mcp&config=JTdCJTIydHJhbnNwb3J0JTIyJTNBJTIyc3RyZWFtYWJsZV9odHRwJTIyJTJDJTIydXJsJTIyJTNBJTIyaHR0cCUzQSUyRiUyRmxvY2FsaG9zdCUzQTMwMDAlMkZtY3AlMjIlMkMlMjJoZWFkZXJzJTIyJTNBJTdCJTdEJTJDJTIydGltZW91dCUyMiUzQTUwJTdE)
```json
{
  "mcpServers": {
      "f2c_mcp": {
        "transport": "streamableHttp",
        "url": "http://localhost:3000/mcp",
        "headers": {
          "personalToken": ""
        },
        "timeout": 50
      }
    }
}
```

### Add SSE
[![Install MCP Server](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/install-mcp?name=f2c_mcp&config=JTdCJTIydHJhbnNwb3J0JTIyJTNBJTIyc3NlJTIyJTJDJTIydXJsJTIyJTNBJTIyaHR0cCUzQSUyRiUyRmxvY2FsaG9zdCUzQTMwMDAlMkZzc2UlMjIlMkMlMjJoZWFkZXJzJTIyJTNBJTdCJTdEJTJDJTIydGltZW91dCUyMiUzQTUwJTdE)
```json
{
  "mcpServers": {
      "f2c_mcp": {
        "transport": "sse",
        "url": "http://localhost:3000/sse",
        "headers": {
          "personalToken": ""
        },
        "timeout": 50
      }
    }
}
```

## Development

### 1. Set up your Figma API key in `.env` file:
```bash
FIGMA_API_KEY=your_api_key_here
```

### 2. Install dependencies:
```bash
bun install
 ```

### 3. Start development server:
### stdio dev server
```bash
bun run dev
 ```
### streamable_http and SSE dev server
```bash
bun run http:dev
 ```

## Install Smithery

To install F2C MCP Server for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@f2c-ai/f2c-mcp):

```bash
npx -y @smithery/cli install @f2c-ai/f2c-mcp --client claude
```
```

--------------------------------------------------------------------------------
/src/server/figma/tools/figma.ts:
--------------------------------------------------------------------------------

```typescript
import api from '@/server/figma/apis/figma'
import type {McpServer} from '@modelcontextprotocol/sdk/server/mcp.js'
import type {CallToolResult} from '@modelcontextprotocol/sdk/types.js'
import {z} from 'zod'

export const registerFigmaServer = (server: McpServer) => {
  // Get Figma file information
  server.tool(
    'figma_get_file_data',
    'Get detailed information about a Figma file',
    {
      fileKey: z.string().describe('Unique identifier of the Figma file'),
      ids: z.string().describe('List of node IDs to retrieve, comma separated'),
      personalToken: z
        .string()
        .optional()
        .describe('Your Figma personal access token, The parameters are not required when the tool is called.'),
      version: z.string().optional().describe('Specify the version to return'),
      depth: z.number().optional().describe('Specify the depth of nodes to return'),
      geometry: z.enum(['paths']).optional().describe('Specify whether to include geometry path data'),
      plugin_data: z.string().optional().describe('Specify plugin data to return'),
      branch_data: z.boolean().optional().describe('Specify whether to return branch data'),
    },
    async (o): Promise<CallToolResult> => {
      try {
        const data = await api.files(o)
        return {
          content: [{type: 'text', text: JSON.stringify(data)}],
        }
      } catch (error: any) {
        return {
          content: [{type: 'text', text: `Error: ${error.message}`}],
        }
      }
    },
  )

  // Get Figma node images
  server.tool(
    'figma_get_images',
    'Get images of Figma nodes',
    {
      fileKey: z.string().describe('Unique identifier of the Figma file'),
      ids: z.string().describe('Node IDs to get images for, comma separated'),
      format: z.enum(['jpg', 'png', 'svg', 'pdf']).optional().describe('Image format, e.g., png, jpg, svg'),
      scale: z.number().optional().describe('Image scale factor'),
      svg_include_id: z.boolean().optional().describe('Whether SVG includes ID'),
      svg_simplify_stroke: z.boolean().optional().describe('Whether to simplify SVG strokes'),
      use_absolute_bounds: z.boolean().optional().describe('Whether to use absolute bounds'),
      version: z.string().optional().describe('Specify the version to return'),
      personalToken: z
        .string()
        .optional()
        .describe('Your Figma personal access token, The parameters are not required when the tool is called.'),
    },
    async (o): Promise<CallToolResult> => {
      try {
        const data = await api.images(o)

        return {
          content: [{type: 'text', text: JSON.stringify(data)}],
        }
      } catch (error: any) {
        return {
          content: [{type: 'text', text: `Error: ${error.message}`}],
        }
      }
    },
  )

  // // Returns download links for all images present in image fills
  // server.tool(
  //   'figma_get_image_fills',
  //   'Get all image resources in the specified Figma file',
  //   {
  //     fileKey: z.string().describe('Unique identifier of the Figma file'),
  //     personalToken: z.string().optional().describe('Your Figma personal access token'),
  //   },
  //   async (o): Promise<CallToolResult> => {
  //     try {
  //       const data = await api.imageFills(o)

  //       return {
  //         content: [{type: 'text', text: JSON.stringify(data)}],
  //       }
  //     } catch (error: any) {
  //       return {
  //         content: [{type: 'text', text: `Error: ${error.message}`}],
  //       }
  //     }
  //   },
  // )

  // // Get Figma file metadata
  // server.tool(
  //   'figma_get_file_meta',
  //   'Get metadata information for a Figma file',
  //   {
  //     fileKey: z.string().describe('Unique identifier of the Figma file'),
  //     personalToken: z.string().optional().describe('Your Figma personal access token'),
  //   },
  //   async (o): Promise<CallToolResult> => {
  //     try {
  //       const data = await api.meta(o)
  //       return {
  //         content: [{type: 'text', text: JSON.stringify(data)}],
  //       }
  //     } catch (error: any) {
  //       return {
  //         content: [{type: 'text', text: `Error: ${error.message}`}],
  //       }
  //     }
  //   },
  // )
}

```

--------------------------------------------------------------------------------
/src/server/figma/tools/f2c.ts:
--------------------------------------------------------------------------------

```typescript
import api from '@/server/figma/apis/f2c'
import {createLogger} from '@/utils/logger'
import type {McpServer} from '@modelcontextprotocol/sdk/server/mcp.js'
import type {CallToolResult} from '@modelcontextprotocol/sdk/types.js'
import type {NodeToCodeFile} from 'src/server/figma/types/f2c'
import {z} from 'zod'
import downloader from '../helpers/downloader'

const logger = createLogger('F2cTool')

export const registerF2cServer = (server: McpServer) => {
  // Register Figma to HTML conversion tool
  server.tool(
    'figma_to_code',
    'Transform Figma designs into production-ready code. This tool converts selected Figma nodes into HTML,enabling seamless design-to-code workflow.',
    {
      fileKey: z
        .string()
        .describe(
          'The Figma file identifier found in the file URL (e.g., https://www.figma.com/file/XXXXXXXXXXXX/). Extract the XXXXXXXXXXXX portion as the fileKey.',
        ),
      ids: z
        .string()
        .describe(
          'Comma-separated list of Figma node IDs for conversion. To obtain node IDs, select elements in Figma, right-click and select "Copy/Paste as" → "Copy ID".',
        ),
      format: z
        .enum(['html', 'react-cssmodules', 'react-tailwind'])
        .default('html')
        .describe(
          'Specify the output format: "html" generates semantic HTML/CSS, "react-cssmodules" creates React components with scoped CSS modules, "react-tailwind" produces React components with utility-first Tailwind classes.',
        ),
      personalToken: z
        .string()
        .optional()
        .describe(
          'Figma personal access token for API authentication.The parameters are not required when the tool is called.',
        ),
      localPath: z
        .string()
        .optional()
        .describe(
          'Absolute path for image asset storage. Directory will be created if non-existent. Path must follow OS-specific format without special character escaping. When set, all static resources will be saved to the images directory under this path.',
        ),
      imgFormat: z
        .enum(['png', 'jpg', 'svg'])
        .default('png')
        .describe(
          'Export format for image assets: "png" for lossless quality, "jpg" for compressed files, or "svg" for vector graphics.',
        ),
      scaleSize: z
        .number()
        .min(1)
        .max(4)
        .default(2)
        .describe(
          'Image export scale factor (1-4). Higher values yield better quality at the cost of larger file sizes.',
        ),
    },
    async (o): Promise<CallToolResult> => {
      downloader.setup({...o, format: 'html'})
      try {
        // const cb: NodeToCodeFile[] = (await api.nodeToCode({...o, format: 'react-tailwind'})) || []
        const cb: NodeToCodeFile[] = (await api.nodeToCode(o)) || []
        await downloader.checkLocalAndDownload(cb)
        if (!cb) {
          return {
            content: [
              {
                type: 'text',
                text: 'Failed to generate any code. Please check if the provided Figma node IDs are correct or if the nodes can be converted to code.',
              },
            ],
          }
        }

        const files = Array.isArray(cb) ? cb : [cb]

        // Handle case when returned file array is empty
        if (files.length === 0) {
          return {
            content: [
              {
                type: 'text',
                text: 'Conversion succeeded but no files were generated. Please check if the selected Figma nodes contain convertible content.',
              },
            ],
          }
        }

        // Create file summary
        const summary = files.map((file, index) => `${index + 1}. ${file.path}`).join('\n')
        // If local path is specified, return save location information instead of detailed content
        if (o.localPath) {
          return {
            content: [
              {
                type: 'text',
                text: `# Files Saved Locally

## Save Location
${o.localPath}

## Generated Files
${summary}. Convert the Tailwind to vanilla CSS if not already used in the codebase. Do not install any dependencies.`,
              },
            ],
          }
        }

        // Create detailed file content (only when local path is not specified)
        const fileDetails = files
          .map((file, index) => {
            const fileExtension = file.path.split('.').pop() || ''
            return `## File ${index + 1}: ${file.path}\n\`\`\`${fileExtension}\n${file.content}\n\`\`\``
          })
          .join('\n\n')

        return {
          content: [
            {
              type: 'text',
              text: `# Generated Files Summary\n${summary}\n\n# File Details\n${fileDetails}. Convert the Tailwind to vanilla CSS if not already used in the codebase. Do not install any dependencies.`,
            },
          ],
        }
      } catch (error: any) {
        logger.error('Tool execution error:', error)
        return {
          content: [{type: 'text', text: `Error: ${error.message}`}],
        }
      }
    },
  )
}

```

--------------------------------------------------------------------------------
/src/transports/streamable-http/http-server.ts:
--------------------------------------------------------------------------------

```typescript
import type {Server} from 'http'
import {randomUUID} from 'node:crypto'
import figmaConfig from '@/server/figma/config'
import {LogLevel, Logger, createLogger} from '@/utils/logger'
import type {McpServer} from '@modelcontextprotocol/sdk/server/mcp.js'
import {SSEServerTransport} from '@modelcontextprotocol/sdk/server/sse.js'
import {StreamableHTTPServerTransport} from '@modelcontextprotocol/sdk/server/streamableHttp.js'
import {isInitializeRequest} from '@modelcontextprotocol/sdk/types.js'
import express, {type Request, type Response} from 'express'

const logger = createLogger('HttpServer', LogLevel.INFO)
let httpServer: Server | null = null
const transports = {
  streamable: {} as Record<string, StreamableHTTPServerTransport>,
  sse: {} as Record<string, SSEServerTransport>,
}
export async function startHttpServer(port: number, mcpServer: McpServer): Promise<void> {
  const app = express()

  // 拦截所有请求,检查并更新personalToken
  app.use((req, res, next) => {
    const personalToken = req.header('personalToken') as string
    //
    // console.log('[personalToken]', personalToken, figmaConfig.personalToken)
    //
    if (personalToken && personalToken.trim() !== '' && personalToken !== figmaConfig.personalToken) {
      logger.debug('Updating Figma personal token from request headers', personalToken, figmaConfig.personalToken)
      figmaConfig.personalToken = personalToken
    }
    next()
  })

  app.use('/mcp', express.json())

  app.post('/mcp', async (req, res) => {
    logger.debug('Received StreamableHTTP request', JSON.stringify(req.headers), JSON.stringify(req.body))
    res.setHeader('Content-Type', 'application/json')
    const sessionId = req.headers['mcp-session-id'] as string | undefined
    let transport: StreamableHTTPServerTransport

    if (sessionId && transports.streamable[sessionId]) {
      logger.debug('Reusing existing StreamableHTTP transport for sessionId', sessionId)
      transport = transports.streamable[sessionId]
    } else if (!sessionId && isInitializeRequest(req.body)) {
      logger.debug('New initialization request for StreamableHTTP sessionId', sessionId)
      transport = new StreamableHTTPServerTransport({
        sessionIdGenerator: () => randomUUID(),
        onsessioninitialized: sessionId => {
          transports.streamable[sessionId] = transport
        },
      })
      transport.onclose = () => {
        if (transport.sessionId) {
          delete transports.streamable[transport.sessionId]
        }
      }
      await mcpServer.connect(transport)
    } else {
      logger.error('Invalid request:', req.body)
      res.status(400).json({
        jsonrpc: '2.0',
        error: {
          code: -32000,
          message: 'Bad Request: No valid session ID provided',
        },
        id: null,
      })
      return
    }

    let progressInterval: NodeJS.Timeout | null = null
    const progressToken = req.body.params?._meta?.progressToken
    let progress = 0
    if (progressToken) {
      logger.info(`Setting up progress notifications for token ${progressToken} on session ${sessionId}`)
      progressInterval = setInterval(async () => {
        logger.info('Sending progress notification', progress)
        await mcpServer.server.notification({
          method: 'notifications/progress',
          params: {
            progress,
            progressToken,
          },
        })
        progress++
      }, 1000)
    }

    logger.debug('Handling StreamableHTTP request')
    await transport.handleRequest(req, res, req.body)

    if (progressInterval) {
      clearInterval(progressInterval)
    }
    logger.debug('StreamableHTTP request handled')
  })

  const handleSessionRequest = async (req: Request, res: Response) => {
    const sessionId = req.headers['mcp-session-id'] as string | undefined
    if (!sessionId || !transports.streamable[sessionId]) {
      res.status(400).send('Invalid or missing session ID')
      return
    }

    logger.info(`Received session termination request for session ${sessionId}`)

    try {
      const transport = transports.streamable[sessionId]
      await transport.handleRequest(req, res)
    } catch (error) {
      logger.error('Error handling session termination:', error)
      if (!res.headersSent) {
        res.status(500).send('Error processing session termination')
      }
    }
  }

  app.get('/mcp', handleSessionRequest)

  app.delete('/mcp', handleSessionRequest)

  app.get('/sse', async (req, res) => {
    logger.debug('Received SSE request', JSON.stringify(req.headers), JSON.stringify(req.body))
    const transport = new SSEServerTransport('/messages', res)
    transports.sse[transport.sessionId] = transport
    res.on('close', () => {
      delete transports.sse[transport.sessionId]
    })
    await mcpServer.connect(transport)
  })

  app.post('/messages', async (req, res) => {
    const sessionId = req.query.sessionId as string
    const transport = transports.sse[sessionId]
    if (transport) {
      await transport.handlePostMessage(req, res)
    } else {
      res.status(400).send(`No transport found for sessionId ${sessionId}`)
      return
    }
  })

  httpServer = app.listen(port, () => {
    logger.info(`SSE endpoint available at http://localhost:${port}/sse`)
    logger.info(`Message endpoint available at http://localhost:${port}/messages`)
    logger.info(`StreamableHTTP endpoint available at http://localhost:${port}/mcp`)
  })

  process.on('SIGINT', async () => {
    logger.debug('Shutting down server...')
    await closeTransports(transports.sse)
    await closeTransports(transports.streamable)

    logger.debug('Server shutdown complete')
    process.exit(0)
  })
}

async function closeTransports(transports: Record<string, SSEServerTransport | StreamableHTTPServerTransport>) {
  for (const sessionId in transports) {
    try {
      await transports[sessionId]?.close()
      delete transports[sessionId]
    } catch (error) {
      logger.error(`Error closing transport for session ${sessionId}:`, error)
    }
  }
}

export async function stopHttpServer(): Promise<void> {
  if (!httpServer) {
    throw new Error('HTTP server is not running')
  }

  return new Promise((resolve, reject) => {
    httpServer!.close((err: Error | undefined) => {
      if (err) {
        reject(err)
        return
      }
      httpServer = null
      const closing = Object.values(transports.sse).map(transport => {
        return transport.close()
      })
      Promise.all(closing).then(() => {
        resolve()
      })
    })
  })
}

```

--------------------------------------------------------------------------------
/src/server/figma/tools/v03.ts:
--------------------------------------------------------------------------------

```typescript
import api from '@/server/figma/apis/f2c'
import figmaApi from '@/server/figma/apis/figma'
import {createLogger} from '@/utils/logger'
import {reportMcpLoader} from '@f2c/data-reporter'
import type {McpServer} from '@modelcontextprotocol/sdk/server/mcp.js'
import type {CallToolResult} from '@modelcontextprotocol/sdk/types.js'
import type {NodeToCodeFile} from 'src/server/figma/types/f2c'
import {z} from 'zod'
import downloader from '../helpers/downloader'
const logger = createLogger('V3Tool')
let ideInfo = ''
export const registerV03Server = (server: McpServer) => {
  reportMcpLoader().then(ide => {
    ideInfo = ide || 'other'
  })
  // Register Figma to HTML conversion tool
  server.tool(
    'get_code',
    'Generate UI code for a given node or the currently selected node in the Figma desktop app. Use the nodeId parameter to specify a node id. If no node id is provided, the currently selected node will be used. If a URL is provided, extract the node id from the URL, for example, if given the URL https://figma.com/design/:fileKey/:fileName?node-id=1-2, the extracted nodeId would be 1:2.',
    {
      fileKey: z
        .string()
        .describe(
          'The Figma file identifier found in the file URL (e.g., https://www.figma.com/file/XXXXXXXXXXXX/). Extract the XXXXXXXXXXXX portion as the fileKey.',
        ),
      ids: z
        .string()
        .describe(
          'Comma-separated list of Figma node IDs for conversion. To obtain node IDs, select elements in Figma, right-click and select "Copy/Paste as" → "Copy ID".',
        ),
      // format: z
      //   .enum(['html', 'react-cssmodules', 'react-tailwind'])
      //   .default('html')
      //   .describe(
      //     'Specify the output format: "html" generates semantic HTML/CSS, "react-cssmodules" creates React components with scoped CSS modules, "react-tailwind" produces React components with utility-first Tailwind classes.',
      //   ),
      personalToken: z
        .string()
        .optional()
        .describe(
          'Figma personal access token for API authentication.The parameters are not required when the tool is called.',
        ),
      localPath: z
        .string()
        .optional()
        .describe(
          'Absolute path for asset(e.g., images) and code storage. Directory will be created if non-existent. Path must follow OS-specific format without special character escaping. When this path is set, all code-related static resources are stored in this directory, while other assets (e.g., images) will be saved into the subdirectory named assets under this path.',
        ),
      imgFormat: z
        .enum(['png', 'jpg', 'svg'])
        .default('png')
        .describe(
          'Export format for image assets: "png" for lossless quality, "jpg" for compressed files, or "svg" for vector graphics.',
        ),
      scaleSize: z
        .number()
        .min(1)
        .max(4)
        .default(1)
        .describe(
          'Image export scale factor (1-4). Higher values yield better quality at the cost of larger file sizes.',
        ),
    },
    async (o): Promise<CallToolResult> => {
      downloader.setup(o)
      try {
        const cb: NodeToCodeFile[] = (await api.nodeToCode({...o, format: 'react-tailwind', ideInfo})) || []

        await downloader.checkLocalAndDownload(cb)
        if (!cb) {
          return {
            content: [
              {
                type: 'text',
                text: 'Failed to generate any code. Please check if the provided Figma node IDs are correct or if the nodes can be converted to code.',
              },
            ],
          }
        }

        const files = Array.isArray(cb) ? cb : [cb]

        // Handle case when returned file array is empty
        if (files.length === 0) {
          return {
            content: [
              {
                type: 'text',
                text: 'Conversion succeeded but no files were generated. Please check if the selected Figma nodes contain convertible content.',
              },
            ],
          }
        }

        // Create file summary
        const summary = files
          .filter(file => !file.path.endsWith('/index.scss'))
          .map((file, index) => `${index + 1}. ${file.path}`)
          .join('\n')
        // If local path is specified, return save location information instead of detailed content
        if (o.localPath) {
          return {
            content: [
              {
                type: 'text',
                text: `# Files Saved Locally

## Save Location
${o.localPath}

## Generated Files
${summary}. Convert the Tailwind to vanilla CSS if not already used in the codebase. Do not install any dependencies.`,
              },
            ],
          }
        }

        // Create detailed file content (only when local path is not specified)
        /* const fileDetails = files
          .map((file, index) => {
            const fileExtension = file.path.split('.').pop() || ''
            return `## File ${index + 1}: ${file.path}\n\`\`\`${fileExtension}\n${file.content}\n\`\`\``
          })
          .join('\n\n') */
        const fileDetails = files
          .filter(file => file.path.endsWith('.tsx'))
          .map(file => file.content)
          .join('\n\n')

        return {
          content: [
            {
              type: 'text',
              text: `${fileDetails} Convert the Tailwind to vanilla CSS if not already used in the codebase. Do not install any dependencies.`,
            },
          ],
        }
      } catch (error: any) {
        logger.error('Tool execution error:', error)
        return {
          content: [{type: 'text', text: `Error: ${error.message}`}],
        }
      }
    },
  )
  server.tool(
    'get_image',
    'Generate an image for a given node or the currently selected node in the Figma desktop app. Use the nodeId parameter to specify a node id. If no node id is provided, the currently selected node will be used. If a URL is provided, extract the node id from the URL, for example, if given the URL https://figma.com/design/:fileKey/:fileName?node-id=1-2, the extracted nodeId would be 1:2. Also export Figma design images for visual verification and design fidelity validation. Essential for comparing generated code output against original designs, ensuring pixel-perfect implementation and catching visual discrepancies during the design-to-code process.',
    {
      fileKey: z.string().describe('Figma file identifier from the URL for accessing the design source'),
      ids: z
        .string()
        .describe(
          'Comma-separated node IDs to export. Use "Copy ID" from Figma context menu to get precise element references for comparison',
        ),
      format: z
        .enum(['jpg', 'png', 'svg', 'pdf'])
        .optional()
        .describe(
          'Export format for verification: "png" for pixel-perfect comparison with transparency, "jpg" for quick previews, "svg" for scalable reference, "pdf" for print validation',
        ),
      scale: z
        .number()
        .optional()
        .describe(
          'Scale factor (1-4x) for high-resolution comparison. Use 2x+ for detailed fidelity checks on retina displays',
        ),
      svg_include_id: z
        .boolean()
        .optional()
        .describe('Include element IDs in SVG for precise element mapping during code validation'),
      svg_simplify_stroke: z
        .boolean()
        .optional()
        .describe('Simplify stroke paths for cleaner reference images during visual comparison'),
      use_absolute_bounds: z
        .boolean()
        .optional()
        .describe('Use absolute positioning for accurate layout verification against implemented code'),
      version: z.string().optional().describe('Specific design version for consistent comparison baseline'),
      personalToken: z
        .string()
        .optional()
        .describe('Figma personal access token for authenticated access to design files'),
    },
    async (o): Promise<CallToolResult> => {
      try {
        const data = await figmaApi.images(o)

        return {
          content: [{type: 'text', text: JSON.stringify(data)}],
        }
      } catch (error: any) {
        return {
          content: [{type: 'text', text: `Error: ${error.message}`}],
        }
      }
    },
  )
}

```