Full Code of MatthewDailey/figma-mcp for AI

main 765155fb7042 cached
9 files
17.1 KB
4.8k tokens
24 symbols
1 requests
Download .txt
Repository: MatthewDailey/figma-mcp
Branch: main
Commit: 765155fb7042
Files: 9
Total size: 17.1 KB

Directory structure:
gitextract_6ugqw82s/

├── .gitignore
├── .prettierrc
├── Dockerfile
├── README.md
├── figma_api.ts
├── index.ts
├── package.json
├── smithery.yaml
└── tsconfig.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
dist
node_modules

================================================
FILE: .prettierrc
================================================
{
  "printWidth": 100,
  "tabWidth": 2,
  "useTabs": false,
  "semi": true,
  "singleQuote": false,
  "trailingComma": "es5",
  "bracketSpacing": true,
  "arrowParens": "always",
  "ignore": ["dist/**/*"]
}


================================================
FILE: Dockerfile
================================================
# Generated by https://smithery.ai. See: https://smithery.ai/docs/build/project-config
FROM node:lts-alpine

# Create app directory
WORKDIR /app

# Install dependencies
COPY package.json package-lock.json tsconfig.json index.ts figma_api.ts ./

# Install deps without running prepare/build scripts
RUN npm ci --ignore-scripts

# Build the project
RUN npm run build

# Copy only built files are already in place because build outputs to dist

# Expose no port needed for stdio

# Default command
CMD ["node", "dist/index.cjs"]


================================================
FILE: README.md
================================================
# Figma MCP Server
[![smithery badge](https://smithery.ai/badge/@MatthewDailey/figma-mcp)](https://smithery.ai/server/@MatthewDailey/figma-mcp)

A [ModelContextProtocol](https://modelcontextprotocol.io) server that enables AI assistants to interact with Figma files. This server provides tools for viewing, commenting, and analyzing Figma designs directly through the ModelContextProtocol.

## Features

- Add a Figma file to your chat with Claude by providing the url
- Read and post comments on Figma files

## Setup with Claude

### Installing via Smithery

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

```bash
npx -y @smithery/cli install @MatthewDailey/figma-mcp --client claude
```

1. Download and install Claude desktop app from [claude.ai/download](https://claude.ai/download)

2. Get a Figma API Key (figma.com -> click your name top left -> settings -> Security). Grant `File content` and `Comments` scopes.

2. Configure Claude to use the Figma MCP server. If this is your first MCP server, run the following in terminal.

```bash
echo '{
  "mcpServers": {
    "figma-mcp": {
      "command": "npx",
      "args": ["figma-mcp"],
      "env": {
        "FIGMA_API_KEY": "<YOUR_API_KEY>"
      }
    }
  }
}' > ~/Library/Application\ Support/Claude/claude_desktop_config.json
```

If it's not, copy the `figma-mcp` block to your `claude_desktop_config.json`

3. Restart Claude Desktop.

4. Look for the hammer icon with the number of available tools in Claude's interface to confirm the server is running.

## Example usage

Start a new chat with claude desktop and paste the following

```
What's in this figma file?

https://www.figma.com/design/MLkM98c1s4A9o9CMnHEyEC
```

## Demo of a more realistic usage

https://www.loom.com/share/0e759622e05e4ab1819325bcf6128945?sid=bcf6125b-b5de-4098-bf81-baff157e3dc3

## Development Setup

### Running with Inspector

For development and debugging purposes, you can use the MCP Inspector tool. The Inspector provides a visual interface for testing and monitoring MCP server interactions.

Visit the [Inspector documentation](https://modelcontextprotocol.io/docs/tools/inspector) for detailed setup instructions and usage guidelines.

The command to test locally with Inspector is
```
npx @modelcontextprotocol/inspector npx figma-mcp
```

### Local Development

1. Clone the repository
2. Install dependencies:
```bash
npm install
```
3. Build the project:
```bash
npm run build
```
4. For development with auto-rebuilding:
```bash
npm run watch
```

## Available Tools

The server provides the following tools:

- `add_figma_file`: Add a Figma file to your context by providing its URL
- `view_node`: Get a thumbnail for a specific node in a Figma file
- `read_comments`: Get all comments on a Figma file
- `post_comment`: Post a comment on a node in a Figma file
- `reply_to_comment`: Reply to an existing comment in a Figma file

Each tool is designed to provide specific functionality for interacting with Figma files through the ModelContextProtocol interface.


================================================
FILE: figma_api.ts
================================================
import axios from "axios";

function getFigmaApiKey() {
  const apiKey = process.env.FIGMA_API_KEY;
  if (!apiKey) {
    throw new Error("FIGMA_API_KEY is not set");
  }
  return apiKey;
}

export function parseKeyFromUrl(url: string) {
  // Extract key from URLs like:
  // https://www.figma.com/board/vJzJ1oVCzowAKAayQJx6Ug/...
  // https://www.figma.com/design/8SvxepW26v4d0AyyTAw23c/...
  // https://www.figma.com/file/8SvxepW26v4d0AyyTAw23c/...
  const matches = url.match(/figma\.com\/(board|design|file)\/([^/?]+)/);
  if (matches) {
    return matches[2]; // Return the second capture group which contains the key
  }
  throw new Error("Could not parse Figma key from URL");
}

type FigNode = {
  id: string;
  name: string;
  type: string;
  children?: FigNode[];
};

type FigFile = {
  name: string;
  version: string;
  document: FigNode;
  thumbnailUrl: string;
  thumbnailB64: string;
};

export function getCanvasIds(figFileJson: FigNode) {
  const canvasIds: string[] = [];
  const queue: FigNode[] = [figFileJson];

  while (queue.length > 0) {
    const node = queue.shift()!;
    if (node.type === "CANVAS") {
      canvasIds.push(node.id);
      continue; // Skip children of canvases
    }
    if (node.children) {
      queue.push(...node.children);
    }
  }
  return canvasIds;
}

export async function downloadFigmaFile(key: string): Promise<FigFile> {
  const response = await axios.get(`https://api.figma.com/v1/files/${key}`, {
    headers: {
      "X-FIGMA-TOKEN": getFigmaApiKey(),
    },
  });
  const data = response.data;
  return {
    ...data,
    thumbnailB64: await imageUrlToBase64(data.thumbnailUrl),
  };
}

export async function getThumbnails(key: string, ids: string[]): Promise<{ [id: string]: string }> {
  const response = await axios.get(
    `https://api.figma.com/v1/images/${key}?ids=${ids.join(",")}&format=png&page_size=1`,
    {
      headers: {
        "X-FIGMA-TOKEN": getFigmaApiKey(),
      },
    }
  );
  const data = response.data as { images: { [id: string]: string }; err?: string };
  if (data.err) {
    throw new Error(`Error getting thumbnails: ${data.err}`);
  }
  return data.images;
}

export async function getThumbnailsOfCanvases(
  key: string,
  document: FigNode
): Promise<{ id: string; url: string; b64: string }[]> {
  const canvasIds = getCanvasIds(document);
  const thumbnails = await getThumbnails(key, canvasIds);
  const results = [];
  for (const [id, url] of Object.entries(thumbnails)) {
    results.push({
      id,
      url,
      b64: await imageUrlToBase64(url),
    });
  }
  return results;
}

export async function readComments(fileKey: string) {
  const response = await axios.get(`https://api.figma.com/v1/files/${fileKey}/comments`, {
    headers: {
      "X-FIGMA-TOKEN": getFigmaApiKey(),
    },
  });
  return response.data;
}

export async function postComment(
  fileKey: string,
  message: string,
  x: number,
  y: number,
  nodeId?: string
) {
  const response = await axios.post(
    `https://api.figma.com/v1/files/${fileKey}/comments`,
    {
      message,
      client_meta: { node_offset: { x, y }, node_id: nodeId },
    },
    {
      headers: {
        "X-FIGMA-TOKEN": getFigmaApiKey(),
        "Content-Type": "application/json",
      },
    }
  );
  return response.data;
}

export async function replyToComment(fileKey: string, commentId: string, message: string) {
  const response = await axios.post(
    `https://api.figma.com/v1/files/${fileKey}/comments`,
    {
      message,
      comment_id: commentId,
    },
    {
      headers: {
        "X-FIGMA-TOKEN": getFigmaApiKey(),
        "Content-Type": "application/json",
      },
    }
  );
  return response.data;
}

async function imageUrlToBase64(url: string) {
  const response = await axios.get(url, { responseType: "arraybuffer" });
  return Buffer.from(response.data).toString("base64");
}


================================================
FILE: index.ts
================================================
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
  CallToolRequestSchema,
  CallToolResult,
  ErrorCode,
  ListToolsRequestSchema,
  McpError,
  Tool,
} from "@modelcontextprotocol/sdk/types.js";
import {
  downloadFigmaFile,
  getThumbnails,
  parseKeyFromUrl,
  postComment,
  readComments,
  replyToComment,
} from "./figma_api.js";

const server = new Server(
  {
    name: "figma-mcp",
    version: "0.1.3",
  },
  {
    capabilities: {
      resources: {},
      tools: {},
      logging: {},
    },
  }
);

const ADD_FIGMA_FILE: Tool = {
  name: "add_figma_file",
  description: "Add a Figma file to your context",
  inputSchema: {
    type: "object",
    properties: {
      url: {
        type: "string",
        description: "The URL of the Figma file to add",
      },
    },
    required: ["url"],
  },
};

const VIEW_NODE: Tool = {
  name: "view_node",
  description: "Get a thumbnail for a specific node in a Figma file",
  inputSchema: {
    type: "object",
    properties: {
      file_key: {
        type: "string",
        description: "The key of the Figma file",
      },
      node_id: {
        type: "string",
        description: "The ID of the node to view. Node ids have the format `<number>:<number>`",
      },
    },
    required: ["file_key", "node_id"],
  },
};

const READ_COMMENTS: Tool = {
  name: "read_comments",
  description: "Get all comments on a Figma file",
  inputSchema: {
    type: "object",
    properties: {
      file_key: {
        type: "string",
        description: "The key of the Figma file",
      },
    },
    required: ["file_key"],
  },
};

const POST_COMMENT: Tool = {
  name: "post_comment",
  description: "Post a comment on a node in a Figma file",
  inputSchema: {
    type: "object",
    properties: {
      file_key: {
        type: "string",
        description: "The key of the Figma file",
      },
      node_id: {
        type: "string",
        description:
          "The ID of the node to comment on. Node ids have the format `<number>:<number>`",
      },
      message: {
        type: "string",
        description: "The comment message",
      },
      x: {
        type: "number",
        description: "The x coordinate of the comment pin",
      },
      y: {
        type: "number",
        description: "The y coordinate of the comment pin",
      },
    },
    required: ["file_key", "message", "x", "y"],
  },
};

const REPLY_TO_COMMENT: Tool = {
  name: "reply_to_comment",
  description: "Reply to an existing comment in a Figma file",
  inputSchema: {
    type: "object",
    properties: {
      file_key: {
        type: "string",
        description: "The key of the Figma file",
      },
      comment_id: {
        type: "string",
        description: "The ID of the comment to reply to. Comment ids have the format `<number>`",
      },
      message: {
        type: "string",
        description: "The reply message",
      },
    },
    required: ["file_key", "comment_id", "message"],
  },
};

server.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [ADD_FIGMA_FILE, VIEW_NODE, READ_COMMENTS, POST_COMMENT, REPLY_TO_COMMENT],
}));

async function doAddFigmaFile(url: string): Promise<CallToolResult> {
  const key = parseKeyFromUrl(url);
  const figFileJson = await downloadFigmaFile(key);
  // Claude seems to error when this is used
  // const thumbnails = await getThumbnailsOfCanvases(key, figFileJson.document);
  return {
    content: [
      {
        type: "text",
        text: JSON.stringify({
          name: figFileJson.name,
          key,
          version: figFileJson.version,
        }),
      },
      {
        type: "text",
        text: "Here is the thumbnail of the Figma file",
      },
      {
        type: "image",
        data: figFileJson.thumbnailB64,
        mimeType: "image/png",
      },
      {
        type: "text",
        text: "Here is the JSON representation of the Figma file",
      },
      {
        type: "text",
        text: JSON.stringify(figFileJson.document),
      },
      {
        type: "text",
        text: "Here are thumbnails of the canvases in the Figma file",
      },
      // ...thumbnails
      //   .map((thumbnail) => [
      //     {
      //       type: "text" as const,
      //       text: `Next is the image of canvas ID: ${thumbnail.id}`,
      //     },
      //     {
      //       type: "image" as const,
      //       data: thumbnail.b64,
      //       mimeType: "image/png",
      //     },
      //   ])
      //   .flat(),
    ],
  };
}

async function doViewNode(fileKey: string, nodeId: string): Promise<CallToolResult> {
  const thumbnails = await getThumbnails(fileKey, [nodeId]);
  const nodeThumb = thumbnails[nodeId];
  if (!nodeThumb) {
    throw new Error(`Could not get thumbnail for node ${nodeId}`);
  }
  const b64 = await imageUrlToBase64(nodeThumb);
  return {
    content: [
      {
        type: "text",
        text: `Thumbnail for node ${nodeId}:`,
      },
      {
        type: "image",
        data: b64,
        mimeType: "image/png",
      },
    ],
  };
}

async function doReadComments(fileKey: string): Promise<CallToolResult> {
  const data = await readComments(fileKey);
  return {
    content: [
      {
        type: "text",
        text: JSON.stringify(data, null, 2),
      },
    ],
  };
}

async function doPostComment(
  fileKey: string,
  message: string,
  x: number,
  y: number,
  nodeId?: string
): Promise<CallToolResult> {
  const data = await postComment(fileKey, message, x, y, nodeId);
  return {
    content: [
      {
        type: "text",
        text: JSON.stringify(data, null, 2),
      },
    ],
  };
}

async function doReplyToComment(
  fileKey: string,
  commentId: string,
  message: string
): Promise<CallToolResult> {
  const data = await replyToComment(fileKey, commentId, message);
  return {
    content: [
      {
        type: "text",
        text: JSON.stringify(data, null, 2),
      },
    ],
  };
}

server.setRequestHandler(CallToolRequestSchema, async (request) => {
  if (request.params.name === "add_figma_file") {
    console.error("Adding Figma file", request.params.arguments);
    const input = request.params.arguments as { url: string };
    return doAddFigmaFile(input.url);
  }

  if (request.params.name === "view_node") {
    const input = request.params.arguments as { file_key: string; node_id: string };
    return doViewNode(input.file_key, input.node_id);
  }

  if (request.params.name === "read_comments") {
    const input = request.params.arguments as { file_key: string };
    return doReadComments(input.file_key);
  }

  if (request.params.name === "post_comment") {
    const input = request.params.arguments as {
      file_key: string;
      node_id?: string;
      message: string;
      x: number;
      y: number;
    };
    return doPostComment(input.file_key, input.message, input.x, input.y, input.node_id);
  }

  if (request.params.name === "reply_to_comment") {
    const input = request.params.arguments as {
      file_key: string;
      comment_id: string;
      message: string;
    };
    return doReplyToComment(input.file_key, input.comment_id, input.message);
  }

  throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}`);
});

server.onerror = (error) => {
  console.error(error);
};

process.on("SIGINT", async () => {
  await server.close();
  process.exit(0);
});

async function runServer() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error("Figma MCP Server running on stdio");
}

runServer().catch((error) => {
  console.error("Fatal error running server:", error);
  process.exit(1);
});

async function imageUrlToBase64(url: string) {
  const response = await fetch(url);
  const arrayBuffer = await response.arrayBuffer();
  return Buffer.from(arrayBuffer).toString("base64");
}


================================================
FILE: package.json
================================================
{
  "name": "figma-mcp",
  "version": "0.1.4",
  "description": "ModelContextProtocol server for Figma",
  "type": "module",
  "scripts": {
    "build": "esbuild index.ts --outfile=dist/index.cjs --bundle --platform=node --format=cjs --banner:js='#!/usr/bin/env node' && shx chmod +x dist/*.cjs",
    "prepare": "npm run build",
    "watch": "esbuild index.ts --outfile=dist/index.cjs --bundle --platform=node --format=cjs --banner:js='#!/usr/bin/env node' --watch"
  },
  "bin": {
    "figma-mcp": "./dist/index.cjs"
  },
  "files": [
    "dist"
  ],
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.0.3",
    "axios": "^1.9.0"
  },
  "devDependencies": {
    "@types/node": "^22.10.1",
    "esbuild": "^0.24.0",
    "prettier": "^3.4.2",
    "shx": "^0.3.4",
    "typescript": "^5.3.3"
  }
}


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

startCommand:
  type: stdio
  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/index.cjs'], env: { FIGMA_API_KEY: config.figmaApiKey } })
  configSchema:
    # JSON Schema defining the configuration options for the MCP.
    type: object
    required:
      - figmaApiKey
    properties:
      figmaApiKey:
        type: string
        description: Figma API key with File content and Comments scopes
  exampleConfig:
    figmaApiKey: ABCD1234EFGH5678IJKL


================================================
FILE: tsconfig.json
================================================
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "outDir": "./dist",
    "rootDir": "."
  },
  "include": [
    "./**/*.ts"
  ],
  "exclude": ["node_modules"]
}
Download .txt
gitextract_6ugqw82s/

├── .gitignore
├── .prettierrc
├── Dockerfile
├── README.md
├── figma_api.ts
├── index.ts
├── package.json
├── smithery.yaml
└── tsconfig.json
Download .txt
SYMBOL INDEX (24 symbols across 2 files)

FILE: figma_api.ts
  function getFigmaApiKey (line 3) | function getFigmaApiKey() {
  function parseKeyFromUrl (line 11) | function parseKeyFromUrl(url: string) {
  type FigNode (line 23) | type FigNode = {
  type FigFile (line 30) | type FigFile = {
  function getCanvasIds (line 38) | function getCanvasIds(figFileJson: FigNode) {
  function downloadFigmaFile (line 55) | async function downloadFigmaFile(key: string): Promise<FigFile> {
  function getThumbnails (line 68) | async function getThumbnails(key: string, ids: string[]): Promise<{ [id:...
  function getThumbnailsOfCanvases (line 84) | async function getThumbnailsOfCanvases(
  function readComments (line 101) | async function readComments(fileKey: string) {
  function postComment (line 110) | async function postComment(
  function replyToComment (line 133) | async function replyToComment(fileKey: string, commentId: string, messag...
  function imageUrlToBase64 (line 150) | async function imageUrlToBase64(url: string) {

FILE: index.ts
  constant ADD_FIGMA_FILE (line 34) | const ADD_FIGMA_FILE: Tool = {
  constant VIEW_NODE (line 49) | const VIEW_NODE: Tool = {
  constant READ_COMMENTS (line 68) | const READ_COMMENTS: Tool = {
  constant POST_COMMENT (line 83) | const POST_COMMENT: Tool = {
  constant REPLY_TO_COMMENT (line 115) | const REPLY_TO_COMMENT: Tool = {
  function doAddFigmaFile (line 142) | async function doAddFigmaFile(url: string): Promise<CallToolResult> {
  function doViewNode (line 195) | async function doViewNode(fileKey: string, nodeId: string): Promise<Call...
  function doReadComments (line 217) | async function doReadComments(fileKey: string): Promise<CallToolResult> {
  function doPostComment (line 229) | async function doPostComment(
  function doReplyToComment (line 247) | async function doReplyToComment(
  function runServer (line 312) | async function runServer() {
  function imageUrlToBase64 (line 323) | async function imageUrlToBase64(url: string) {
Condensed preview — 9 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (19K chars).
[
  {
    "path": ".gitignore",
    "chars": 17,
    "preview": "dist\nnode_modules"
  },
  {
    "path": ".prettierrc",
    "chars": 207,
    "preview": "{\n  \"printWidth\": 100,\n  \"tabWidth\": 2,\n  \"useTabs\": false,\n  \"semi\": true,\n  \"singleQuote\": false,\n  \"trailingComma\": \""
  },
  {
    "path": "Dockerfile",
    "chars": 526,
    "preview": "# Generated by https://smithery.ai. See: https://smithery.ai/docs/build/project-config\nFROM node:lts-alpine\n\n# Create ap"
  },
  {
    "path": "README.md",
    "chars": 3107,
    "preview": "# Figma MCP Server\n[![smithery badge](https://smithery.ai/badge/@MatthewDailey/figma-mcp)](https://smithery.ai/server/@M"
  },
  {
    "path": "figma_api.ts",
    "chars": 3870,
    "preview": "import axios from \"axios\";\n\nfunction getFigmaApiKey() {\n  const apiKey = process.env.FIGMA_API_KEY;\n  if (!apiKey) {\n   "
  },
  {
    "path": "index.ts",
    "chars": 7987,
    "preview": "import { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport { StdioServerTransport } from \"@modelcontextp"
  },
  {
    "path": "package.json",
    "chars": 802,
    "preview": "{\n  \"name\": \"figma-mcp\",\n  \"version\": \"0.1.4\",\n  \"description\": \"ModelContextProtocol server for Figma\",\n  \"type\": \"modu"
  },
  {
    "path": "smithery.yaml",
    "chars": 660,
    "preview": "# Smithery configuration file: https://smithery.ai/docs/build/project-config\n\nstartCommand:\n  type: stdio\n  commandFunct"
  },
  {
    "path": "tsconfig.json",
    "chars": 375,
    "preview": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2022\",\n    \"module\": \"Node16\",\n    \"moduleResolution\": \"Node16\",\n    \"strict\":"
  }
]

About this extraction

This page contains the full source code of the MatthewDailey/figma-mcp GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 9 files (17.1 KB), approximately 4.8k tokens, and a symbol index with 24 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!