figma-mcp-server 0.0.0-alpha.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/Dockerfile ADDED
@@ -0,0 +1,9 @@
1
+ FROM node:22.12-alpine AS builder
2
+
3
+ WORKDIR /app
4
+ COPY package.json package-lock.json ./
5
+ RUN npm install
6
+
7
+ COPY . .
8
+
9
+ ENTRYPOINT ["node", "mcpServer.js"]
package/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Abhimanyu Rana @planetabhi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, and/or publish copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
package/README.md CHANGED
@@ -1,85 +1,54 @@
1
1
  # Figma MCP Server
2
+ MCP server for Figma
2
3
 
3
- ### 📥 Installation & Setup
4
-
5
- **1. Install dependencies**
6
-
7
- Run from your project's root directory:
4
+ ## Install
5
+ Install the server
8
6
 
9
7
  ```sh
10
- npm install
8
+ git clone https://github.com/planetabhi/figma-mcp-server.git
9
+ cd figma-mcp-server
10
+ pnpm i
11
11
  ```
12
12
 
13
- ### 🔐 Set tool environment variables
14
-
15
- In the `.env` file, you'll see environment variable placeholders, one for each workspace that the selected tools are from. For example, if you selected requests from 2 workspaces, e.g. Acme and Widgets, you'll see two placeholders:
13
+ ### Set tool environment variables
14
+ In the `.env` file, set the `FIGMA_API_KEY` to your Figma API key.
16
15
 
17
16
  ```
18
- ACME_API_KEY=
19
- WIDGETS_API_KEY=
17
+ FIGMA_API_KEY=
20
18
  ```
21
19
 
22
- Update the values with actual API keys for each API. These environment variables are used inside of the generated tools to set the API key for each request. You can inspect a file in the `tools` directory to see how it works.
23
-
24
- ```javascript
25
- // environment variables are used inside of each tool file
26
- const apiKey = process.env.ACME_API_KEY;
27
- ```
28
-
29
- **Caveat:** This may not be correct for every API. The generation logic is relatively simple - for each workspace, we create an environment variable with the same name as the workspace slug, and then use that environment variable in each tool file that belongs to that workspace. If this isn't the right behavior for your chosen API, no problem! You can manually update anything in the `.env` file or tool files to accurately reflect the API's method of authentication.
30
-
31
- ### 🛠️ List Available Tools
32
-
33
- List descriptions and parameters from all generated tools with:
20
+ ### List Figma Tools
21
+ List descriptions and parameters from all available Figma tools
34
22
 
35
23
  ```sh
36
- node index.js tools
37
- ```
38
-
39
- Example:
40
-
41
- ```
42
- Available Tools:
43
-
44
- Workspace: acme-workspace
45
- Collection: useful-api
46
- list_all_customers
47
- Description: Retrieve a list of useful things.
48
- Parameters:
49
- - magic: The required magic power
50
- - limit: Number of results returned
51
- [...additional parameters...]
24
+ pnpm list-tools
52
25
  ```
53
26
 
54
- ## 🌐 Running the MCP Server
55
-
56
- The MCP Server (`mcpServer.js`) exposes your automated API tools to MCP-compatible clients, such as Claude Desktop or the Postman Desktop Application.
57
-
58
- ### A) 🖥️ Run with Postman
27
+ ## Run the MCP Server
59
28
 
60
- The Postman Desktop Application is the easiest way to run and test MCP servers.
29
+ MCP Server `mcpServer.js` exposes your Figma API tools to MCP-compatible clients.
61
30
 
62
- Step 1: Download the latest Postman Desktop Application from [https://www.postman.com/downloads/](https://www.postman.com/downloads/).
31
+ 1. Find node path: `which node`
32
+ 2. Find mcpServer.js path: `realpath mcpServer.js`
63
33
 
64
- Step 2: Read out the documentation article [here](https://learning.postman.com/docs/postman-ai-agent-builder/mcp-requests/overview/) for the next steps.
34
+ ### Run with Postman
65
35
 
66
- ### B) 👩‍💻 Run with Claude Desktop
36
+ Postman desktop app is the easiest way to [run and test MCP servers](https://learning.postman.com/docs/postman-ai-agent-builder/mcp-requests/overview/).
67
37
 
68
- To integrate with Claude Desktop:
69
-
70
- 1. Find node path:
38
+ 1. Choose an existing workspace or create a new one.
39
+ 2. Select New > MCP icon MCP. Postman opens a new MCP request in a new tab.
40
+ 3. Select the server's communication method STDIO.
41
+ 4. Enter the server's command and arguments.
71
42
 
72
43
  ```sh
73
- which node
44
+ STDIO <absolute_path_to_node> <absolute_path_to_mcpServer.js>
74
45
  ```
75
46
 
76
- 2. Find `mcpServer.js` path:
47
+ Or you can fork Postman [collection here](https://www.postman.com/doitagain/workspace/figma/collection/68369062465421c338809955?action=share&creator=17652550).
77
48
 
78
- ```sh
79
- realpath mcpServer.js
80
- ```
49
+ ### Run with Claude Desktop
81
50
 
82
- 3. Open Claude Desktop → **Settings** → **Developers** → **Edit Config** and add your server:
51
+ 1. Open Claude Desktop → **Settings** → **Developers** → **Edit Config** and add your server:
83
52
 
84
53
  ```json
85
54
  {
@@ -92,11 +61,22 @@ realpath mcpServer.js
92
61
  }
93
62
  ```
94
63
 
95
- Restart Claude Desktop to activate this change.
64
+ 2. Restart Claude Desktop to activate this change.
65
+
66
+ #### Try it out:
67
+
68
+ 1. Open Claude Desktop, then click on the search and tools icon button and select your server name from the list.
69
+ 2. Enable the `get_design_node` tool from the tools list.
70
+ 3. Copy a design node link from a Figma file, then paste it in Claude Desktop.
71
+ 4. It will return the design node data and other information.
72
+
73
+ > Note: Some tools may be non-functional at the moment because of changes to the Figma API. Working on updating the endpoints in future updates.
74
+
75
+ ---
96
76
 
97
77
  ### Additional Options
98
78
 
99
- #### 🐳 Docker Deployment (Production)
79
+ #### Docker Deployment (Production)
100
80
 
101
81
  For production deployments, you can use Docker:
102
82
 
@@ -123,41 +103,10 @@ Add Docker server configuration to Claude Desktop (Settings → Developers → E
123
103
 
124
104
  > Add your environment variables (API keys, etc.) inside the `.env` file.
125
105
 
126
- #### 🌐 Server-Sent Events (SSE)
106
+ #### Server-Sent Events (SSE)
127
107
 
128
108
  To run the server with Server-Sent Events (SSE) support, use the `--sse` flag:
129
109
 
130
110
  ```sh
131
111
  node mcpServer.js --sse
132
- ```
133
-
134
- ## 🐳 Dockerfile (Included)
135
-
136
- The project comes bundled with the following minimal Docker setup:
137
-
138
- ```dockerfile
139
- FROM node:22.12-alpine AS builder
140
-
141
- WORKDIR /app
142
- COPY package.json package-lock.json ./
143
- RUN npm install
144
-
145
- COPY . .
146
-
147
- ENTRYPOINT ["node", "mcpServer.js"]
148
- ```
149
-
150
- ## ➕ Adding New Tools
151
-
152
- Extend your agent with more tools easily:
153
-
154
- 1. Visit [Postman Agent Generator](https://postman.com/explore/agent-generator).
155
- 2. Pick new API request(s), generate a new agent, and download it.
156
- 3. Copy new generated tool(s) into your existing project's `tools/` folder.
157
- 4. Update your `tools/paths.js` file to include new tool references.
158
-
159
- ## 💬 Questions & Support
160
-
161
- Visit the [Postman Agent Generator](https://postman.com/explore/agent-generator) page for updates and new capabilities.
162
-
163
- Visit the [Postman Community](https://community.postman.com/) to share what you've built, ask questions and get help.
112
+ ```
@@ -0,0 +1,65 @@
1
+ import { discoverTools } from "../lib/tools.js";
2
+
3
+ export function registerToolsCommand(program) {
4
+ program
5
+ .command("tools")
6
+ .description("List all available API tools")
7
+ .action(async () => {
8
+ const tools = await discoverTools();
9
+ if (tools.length === 0) {
10
+ console.log("No tools found. Tools should be organized as:");
11
+ console.log("tools/workspace/collection/request.js\n");
12
+ return;
13
+ }
14
+
15
+ console.log("\nAvailable Tools:\n");
16
+
17
+ // Group tools by workspace/collection
18
+ const groupedTools = tools.reduce((acc, tool) => {
19
+ // Extract workspace and collection from path
20
+ const parts = tool.path.split("/");
21
+ const workspace = parts[1] || "Unknown Workspace";
22
+ const collection = parts[2] || "Unknown Collection";
23
+
24
+ if (!acc[workspace]) acc[workspace] = {};
25
+ if (!acc[workspace][collection]) acc[workspace][collection] = [];
26
+
27
+ acc[workspace][collection].push(tool);
28
+ return acc;
29
+ }, {});
30
+
31
+ // Print tools in a hierarchical structure
32
+ for (const [workspace, collections] of Object.entries(groupedTools)) {
33
+ console.log(`Workspace: ${workspace}`);
34
+ for (const [collection, tools] of Object.entries(collections)) {
35
+ console.log(` Collection: ${collection}`);
36
+ tools.forEach(
37
+ ({
38
+ definition: {
39
+ function: { name, description, parameters },
40
+ },
41
+ }) => {
42
+ console.log(` ${name}`);
43
+ console.log(
44
+ ` Description: ${description || "No description provided"}`
45
+ );
46
+ if (parameters?.properties) {
47
+ console.log(" Parameters:");
48
+ Object.entries(parameters.properties).forEach(
49
+ ([name, details]) => {
50
+ console.log(
51
+ ` - ${name}: ${
52
+ details.description || "No description"
53
+ }`
54
+ );
55
+ }
56
+ );
57
+ }
58
+ console.log("");
59
+ }
60
+ );
61
+ }
62
+ console.log("");
63
+ }
64
+ });
65
+ }
package/lib/tools.js ADDED
@@ -0,0 +1,16 @@
1
+ import { toolPaths } from "../tools/paths.js";
2
+
3
+ /**
4
+ * Discovers and loads available tools from the tools directory
5
+ * @returns {Promise<Array>} Array of tool objects
6
+ */
7
+ export async function discoverTools() {
8
+ const toolPromises = toolPaths.map(async (file) => {
9
+ const module = await import(`../tools/${file}`);
10
+ return {
11
+ ...module.apiTool,
12
+ path: file,
13
+ };
14
+ });
15
+ return Promise.all(toolPromises);
16
+ }
package/mcpServer.js ADDED
@@ -0,0 +1,145 @@
1
+ #!/usr/bin/env node
2
+ import dotenv from "dotenv";
3
+ import express from "express";
4
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
5
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
+ import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
7
+ import {
8
+ CallToolRequestSchema,
9
+ ErrorCode,
10
+ ListToolsRequestSchema,
11
+ McpError,
12
+ } from "@modelcontextprotocol/sdk/types.js";
13
+ import { discoverTools } from "./lib/tools.js";
14
+
15
+ import path from "path";
16
+ import { fileURLToPath } from "url";
17
+
18
+ const __filename = fileURLToPath(import.meta.url);
19
+ const __dirname = path.dirname(__filename);
20
+
21
+ dotenv.config({ path: path.resolve(__dirname, ".env") });
22
+
23
+ const SERVER_NAME = "generated-mcp-server";
24
+
25
+ async function transformTools(tools) {
26
+ return tools
27
+ .map((tool) => {
28
+ const definitionFunction = tool.definition?.function;
29
+ if (!definitionFunction) return;
30
+ return {
31
+ name: definitionFunction.name,
32
+ description: definitionFunction.description,
33
+ inputSchema: definitionFunction.parameters,
34
+ };
35
+ })
36
+ .filter(Boolean);
37
+ }
38
+
39
+ async function run() {
40
+ const args = process.argv.slice(2);
41
+ const isSSE = args.includes("--sse");
42
+
43
+ const server = new Server(
44
+ {
45
+ name: SERVER_NAME,
46
+ version: "0.1.0",
47
+ },
48
+ {
49
+ capabilities: {
50
+ tools: {},
51
+ },
52
+ }
53
+ );
54
+
55
+ server.onerror = (error) => console.error("[Error]", error);
56
+
57
+ // Gracefully shutdown on SIGINT
58
+ process.on("SIGINT", async () => {
59
+ await server.close();
60
+ process.exit(0);
61
+ });
62
+
63
+ const tools = await discoverTools();
64
+
65
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
66
+ tools: await transformTools(tools),
67
+ }));
68
+
69
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
70
+ const toolName = request.params.name;
71
+ const tool = tools.find((t) => t.definition.function.name === toolName);
72
+
73
+ if (!tool) {
74
+ throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${toolName}`);
75
+ }
76
+
77
+ const args = request.params.arguments;
78
+ const requiredParameters =
79
+ tool.definition?.function?.parameters?.required || [];
80
+
81
+ for (const requiredParameter of requiredParameters) {
82
+ if (!(requiredParameter in args)) {
83
+ throw new McpError(
84
+ ErrorCode.InvalidParams,
85
+ `Missing required parameter: ${requiredParameter}`
86
+ );
87
+ }
88
+ }
89
+
90
+ try {
91
+ const result = await tool.function(args);
92
+ return {
93
+ content: [
94
+ {
95
+ type: "text",
96
+ text: JSON.stringify(result, null, 2),
97
+ },
98
+ ],
99
+ };
100
+ } catch (error) {
101
+ console.error("[Error] Failed to fetch data:", error);
102
+ throw new McpError(
103
+ ErrorCode.InternalError,
104
+ `API error: ${error.message}`
105
+ );
106
+ }
107
+ });
108
+
109
+ if (isSSE) {
110
+ const app = express();
111
+ const transports = {};
112
+
113
+ app.get("/sse", async (_req, res) => {
114
+ const transport = new SSEServerTransport("/messages", res);
115
+ transports[transport.sessionId] = transport;
116
+
117
+ res.on("close", () => {
118
+ delete transports[transport.sessionId];
119
+ });
120
+
121
+ await server.connect(transport);
122
+ });
123
+
124
+ app.post("/messages", async (req, res) => {
125
+ const sessionId = req.query.sessionId;
126
+ const transport = transports[sessionId];
127
+
128
+ if (transport) {
129
+ await transport.handlePostMessage(req, res);
130
+ } else {
131
+ res.status(400).send("No transport found for sessionId");
132
+ }
133
+ });
134
+
135
+ const port = process.env.PORT || 3001;
136
+ app.listen(port, () => {
137
+ console.log(`[SSE Server] running on port ${port}`);
138
+ });
139
+ } else {
140
+ const transport = new StdioServerTransport();
141
+ await server.connect(transport);
142
+ }
143
+ }
144
+
145
+ run().catch(console.error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "figma-mcp-server",
3
- "version": "0.0.0-alpha.1",
3
+ "version": "0.1.0",
4
4
  "description": "MCP server for Figma",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -24,7 +24,12 @@
24
24
  ],
25
25
  "author": "@planetabhi",
26
26
  "license": "MIT",
27
- "files": [
28
- "README.md"
29
- ]
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "git+https://github.com/planetabhi/figma-mcp-server.git"
30
+ },
31
+ "bugs": {
32
+ "url": "https://github.com/planetabhi/figma-mcp-server/issues"
33
+ },
34
+ "homepage": "https://github.com/planetabhi/figma-mcp-server#readme"
30
35
  }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Function to add a webhook in Figma.
3
+ *
4
+ * @returns {Promise<Object>} - The result of the webhook addition.
5
+ */
6
+ const executeFunction = async () => {
7
+ const webhookUrl = 'https://api.figma.com/v2/webhooks';
8
+ const token = process.env.FIGMA_API_KEY;
9
+ try {
10
+ // Set up headers for the request
11
+ const headers = {
12
+ 'X-Figma-Token': token,
13
+ 'Content-Type': 'application/json'
14
+ };
15
+
16
+ // Perform the fetch request
17
+ const response = await fetch(webhookUrl, {
18
+ method: 'POST',
19
+ headers
20
+ });
21
+
22
+ // Check if the response was successful
23
+ if (!response.ok) {
24
+ const errorData = await response.json();
25
+ throw new Error(errorData);
26
+ }
27
+
28
+ // Parse and return the response data
29
+ const data = await response.json();
30
+ return data;
31
+ } catch (error) {
32
+ console.error('Error adding webhook:', error);
33
+ return { error: 'An error occurred while adding the webhook.' };
34
+ }
35
+ };
36
+
37
+ /**
38
+ * Tool configuration for adding a webhook in Figma.
39
+ * @type {Object}
40
+ */
41
+ const apiTool = {
42
+ function: executeFunction,
43
+ definition: {
44
+ type: 'function',
45
+ function: {
46
+ name: 'add_webhook',
47
+ description: 'Add a webhook in Figma.',
48
+ parameters: {
49
+ type: 'object',
50
+ properties: {},
51
+ required: []
52
+ }
53
+ }
54
+ }
55
+ };
56
+
57
+ export { apiTool };
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Function to create a variable collection in Figma.
3
+ *
4
+ * @param {Object} args - Arguments for creating the variable collection.
5
+ * @param {string} args.file_key - The key of the Figma file where the variable collection will be created.
6
+ * @param {string} args.name - The name of the new variable collection to be created.
7
+ * @returns {Promise<Object>} - The result of the variable collection creation.
8
+ */
9
+ const executeFunction = async ({ file_key, name }) => {
10
+ const baseUrl = 'https://api.figma.com/v1';
11
+ const token = process.env.FIGMA_API_KEY;
12
+
13
+ try {
14
+ // Construct the URL for the request
15
+ const url = `${baseUrl}/files/${file_key}/variables`;
16
+
17
+ // Set up headers for the request
18
+ const headers = {
19
+ 'X-Figma-Token': token,
20
+ 'Content-Type': 'application/json'
21
+ };
22
+
23
+ // Create the request body
24
+ const body = JSON.stringify({
25
+ variableCollections: [
26
+ {
27
+ action: 'CREATE',
28
+ name: name
29
+ }
30
+ ]
31
+ });
32
+
33
+ // Perform the fetch request
34
+ const response = await fetch(url, {
35
+ method: 'POST',
36
+ headers,
37
+ body
38
+ });
39
+
40
+ // Check if the response was successful
41
+ if (!response.ok) {
42
+ const errorData = await response.json();
43
+ throw new Error(errorData);
44
+ }
45
+
46
+ // Parse and return the response data
47
+ const data = await response.json();
48
+ return data;
49
+ } catch (error) {
50
+ console.error('Error creating variable collection:', error);
51
+ return { error: 'An error occurred while creating the variable collection.' };
52
+ }
53
+ };
54
+
55
+ /**
56
+ * Tool configuration for creating a variable collection in Figma.
57
+ * @type {Object}
58
+ */
59
+ const apiTool = {
60
+ function: executeFunction,
61
+ definition: {
62
+ type: 'function',
63
+ function: {
64
+ name: 'create_variable_collection',
65
+ description: 'Create a new variable collection in Figma.',
66
+ parameters: {
67
+ type: 'object',
68
+ properties: {
69
+ file_key: {
70
+ type: 'string',
71
+ description: 'The key of the Figma file where the variable collection will be created.'
72
+ },
73
+ name: {
74
+ type: 'string',
75
+ description: 'The name of the new variable collection to be created.'
76
+ }
77
+ },
78
+ required: ['file_key', 'name']
79
+ }
80
+ }
81
+ }
82
+ };
83
+
84
+ export { apiTool };
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Function to delete a specific webhook from Figma.
3
+ *
4
+ * @param {Object} args - Arguments for the deletion.
5
+ * @param {string} args.webhook_id - The ID of the webhook to delete.
6
+ * @returns {Promise<Object>} - The result of the deletion operation.
7
+ */
8
+ const executeFunction = async ({ webhook_id }) => {
9
+ const baseUrl = 'https://api.figma.com/v1';
10
+ const token = process.env.FIGMA_API_KEY;
11
+ const webhookUrl = `${baseUrl}/webhooks/${webhook_id}`;
12
+
13
+ try {
14
+ // Set up headers for the request
15
+ const headers = {
16
+ 'X-Figma-Token': token
17
+ };
18
+
19
+ // Perform the fetch request
20
+ const response = await fetch(webhookUrl, {
21
+ method: 'DELETE',
22
+ headers
23
+ });
24
+
25
+ // Check if the response was successful
26
+ if (!response.ok) {
27
+ const errorData = await response.json();
28
+ throw new Error(errorData);
29
+ }
30
+
31
+ // Return the response data (should be empty for successful deletion)
32
+ return {};
33
+ } catch (error) {
34
+ console.error('Error deleting webhook:', error);
35
+ return { error: 'An error occurred while deleting the webhook.' };
36
+ }
37
+ };
38
+
39
+ /**
40
+ * Tool configuration for deleting a webhook from Figma.
41
+ * @type {Object}
42
+ */
43
+ const apiTool = {
44
+ function: executeFunction,
45
+ definition: {
46
+ type: 'function',
47
+ function: {
48
+ name: 'delete_webhook',
49
+ description: 'Delete a specific webhook from Figma.',
50
+ parameters: {
51
+ type: 'object',
52
+ properties: {
53
+ webhook_id: {
54
+ type: 'string',
55
+ description: 'The ID of the webhook to delete.'
56
+ }
57
+ },
58
+ required: ['webhook_id']
59
+ }
60
+ }
61
+ }
62
+ };
63
+
64
+ export { apiTool };