IgnorantesNaturales commited on
Commit
1c2fe57
·
1 Parent(s): b0183f0

Add new features to MCP

Browse files
Files changed (5) hide show
  1. HUGGINGFACE_PUBLISHING.md +194 -0
  2. README.huggingface.md +151 -54
  3. README.md +204 -73
  4. index.js +1845 -193
  5. package.json +2 -2
HUGGINGFACE_PUBLISHING.md ADDED
@@ -0,0 +1,194 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Guía de Publicación en Hugging Face
2
+
3
+ ## 📋 Prerrequisitos
4
+
5
+ 1. Cuenta en Hugging Face: https://huggingface.co/join
6
+ 2. Git instalado
7
+ 3. Git LFS instalado (opcional pero recomendado)
8
+
9
+ ## 🚀 Método 1: Crear Space Directamente en Hugging Face
10
+
11
+ ### Paso 1: Crear el Space
12
+
13
+ 1. Ve a https://huggingface.co/new-space
14
+ 2. Configura:
15
+ - **Space name**: `mcp-figma-rest-api` (o el nombre que prefieras)
16
+ - **License**: MIT
17
+ - **Space SDK**: Static
18
+ - **Visibility**: Public
19
+
20
+ ### Paso 2: Clonar y Subir Contenido
21
+
22
+ ```bash
23
+ # Clonar el Space vacío
24
+ git clone https://huggingface.co/spaces/TU_USUARIO/mcp-figma-rest-api
25
+ cd mcp-figma-rest-api
26
+
27
+ # Copiar el README preparado para Hugging Face
28
+ cp ../mcp-figma-comment-summary/README.huggingface.md README.md
29
+
30
+ # Añadir y publicar
31
+ git add README.md
32
+ git commit -m "Add Complete Figma REST API MCP Server v2.0.0"
33
+ git push
34
+ ```
35
+
36
+ ### Paso 3: Verificar
37
+
38
+ Ve a `https://huggingface.co/spaces/TU_USUARIO/mcp-figma-rest-api` y verifica que todo se vea correctamente.
39
+
40
+ ---
41
+
42
+ ## 🔗 Método 2: Sincronizar desde GitHub
43
+
44
+ ### Opción A: Importar Repositorio Existente
45
+
46
+ 1. Ve a https://huggingface.co/new-space
47
+ 2. Selecciona **"Import from GitHub"**
48
+ 3. Conecta tu cuenta de GitHub
49
+ 4. Selecciona tu repositorio `mcp-figma-comment-summary`
50
+ 5. Hugging Face clonará automáticamente
51
+
52
+ **IMPORTANTE:** Si usas este método, necesitas renombrar `README.huggingface.md` a `README.md` en tu repositorio de GitHub primero.
53
+
54
+ ### Opción B: Configurar Sincronización Bidireccional
55
+
56
+ Si quieres mantener GitHub como fuente principal y que Hugging Face se sincronice automáticamente:
57
+
58
+ ```bash
59
+ # En tu repositorio local
60
+ cd /Users/fran/Documents/IN/Labs/mcp-figma-comment-summary
61
+
62
+ # Añadir Hugging Face como remote adicional
63
+ git remote add huggingface https://huggingface.co/spaces/TU_USUARIO/mcp-figma-rest-api
64
+
65
+ # Crear una rama específica para Hugging Face (opcional)
66
+ git checkout -b huggingface-sync
67
+
68
+ # Renombrar README para Hugging Face
69
+ mv README.md README.github.md
70
+ mv README.huggingface.md README.md
71
+
72
+ # Commit y push a Hugging Face
73
+ git add .
74
+ git commit -m "Prepare for Hugging Face deployment"
75
+ git push huggingface huggingface-sync:main
76
+
77
+ # Volver a la rama principal
78
+ git checkout main
79
+ ```
80
+
81
+ ---
82
+
83
+ ## 📝 Personalizar la Página de Hugging Face
84
+
85
+ El archivo `README.huggingface.md` ya tiene los metadatos necesarios en el frontmatter YAML:
86
+
87
+ ```yaml
88
+ ---
89
+ title: Figma REST API MCP Server
90
+ emoji: 🎨
91
+ colorFrom: purple
92
+ colorTo: blue
93
+ sdk: static
94
+ pinned: false
95
+ tags:
96
+ - mcp
97
+ - model-context-protocol
98
+ - figma
99
+ - api
100
+ - design
101
+ - collaboration
102
+ - claude
103
+ - webhooks
104
+ - variables
105
+ license: mit
106
+ ---
107
+ ```
108
+
109
+ Puedes personalizar:
110
+ - **title**: Nombre que aparece en Hugging Face
111
+ - **emoji**: Icono del Space
112
+ - **colorFrom/colorTo**: Gradiente del header
113
+ - **tags**: Para búsqueda y descubrimiento
114
+ - **pinned**: Si quieres que aparezca destacado en tu perfil
115
+
116
+ ---
117
+
118
+ ## 🔍 Verificación Post-Publicación
119
+
120
+ Después de publicar, verifica:
121
+
122
+ 1. ✅ El README se muestra correctamente
123
+ 2. ✅ Los 29 endpoints están documentados
124
+ 3. ✅ Los ejemplos de uso funcionan
125
+ 4. ✅ Los enlaces a npm y GitHub funcionan
126
+ 5. ✅ Las instrucciones de instalación son claras
127
+
128
+ ---
129
+
130
+ ## 📦 Actualizar en el Futuro
131
+
132
+ Cuando publiques actualizaciones:
133
+
134
+ ```bash
135
+ # Si usas el método directo
136
+ cd mcp-figma-rest-api
137
+ # Actualiza README.md con los cambios
138
+ git add .
139
+ git commit -m "Update to v2.0.1"
140
+ git push
141
+
142
+ # Si sincronizas desde GitHub
143
+ git push github main
144
+ git push huggingface huggingface-sync:main
145
+ ```
146
+
147
+ ---
148
+
149
+ ## 🌟 Promoción
150
+
151
+ Una vez publicado, puedes:
152
+
153
+ 1. Compartir el enlace en:
154
+ - Twitter/X con hashtags #MCP #Figma #Claude
155
+ - Discord de Model Context Protocol
156
+ - Comunidad de Figma
157
+
158
+ 2. Añadir badge en tu README de GitHub:
159
+ ```markdown
160
+ [![Hugging Face](https://img.shields.io/badge/🤗%20Hugging%20Face-Spaces-blue)](https://huggingface.co/spaces/TU_USUARIO/mcp-figma-rest-api)
161
+ ```
162
+
163
+ 3. Enlazar desde tu perfil de npm:
164
+ - Actualiza el `homepage` en `package.json` si quieres que apunte a Hugging Face
165
+
166
+ ---
167
+
168
+ ## ❓ Troubleshooting
169
+
170
+ ### El README no se muestra correctamente
171
+ - Verifica que el archivo se llama exactamente `README.md`
172
+ - Revisa que el frontmatter YAML esté correcto
173
+ - Comprueba que no haya caracteres especiales mal formateados
174
+
175
+ ### Los cambios no se reflejan
176
+ - Espera 1-2 minutos (Hugging Face puede tardar en actualizar)
177
+ - Haz un hard refresh (Cmd+Shift+R en Mac, Ctrl+Shift+R en Windows)
178
+ - Verifica que el push se completó correctamente
179
+
180
+ ### Error de permisos
181
+ - Asegúrate de estar autenticado en Hugging Face
182
+ - Configura tu token de acceso si es necesario:
183
+ ```bash
184
+ git config credential.helper store
185
+ ```
186
+
187
+ ---
188
+
189
+ ## 📚 Referencias
190
+
191
+ - [Hugging Face Spaces Documentation](https://huggingface.co/docs/hub/spaces)
192
+ - [Hugging Face Git LFS](https://huggingface.co/docs/hub/repositories-getting-started#terminal)
193
+ - [Model Context Protocol](https://modelcontextprotocol.io/)
194
+ - [Figma REST API Docs](https://developers.figma.com/docs/rest-api/)
README.huggingface.md CHANGED
@@ -1,6 +1,6 @@
1
  ---
2
- title: Figma Comment Summary MCP
3
- emoji: 💬
4
  colorFrom: purple
5
  colorTo: blue
6
  sdk: static
@@ -9,32 +9,54 @@ tags:
9
  - mcp
10
  - model-context-protocol
11
  - figma
12
- - comments
13
  - design
14
  - collaboration
15
  - claude
 
 
16
  license: mit
17
  ---
18
 
19
- # 💬 Figma Comment Summary MCP Server
20
 
21
- A [Model Context Protocol](https://modelcontextprotocol.io/) server that enables LLMs like Claude to **read, write, and analyze** comments in Figma files. Perfect for design review workflows, team collaboration analysis, and automated comment management.
22
 
23
- ## 🎯 Capabilities
24
 
25
- **📖 Read Operations:**
26
- - Retrieve all comments with full details (text, author, timestamp, location)
27
- - Generate automatic summaries and statistics
 
 
28
 
29
- **✍️ Write Operations:**
30
- - Create new comments anywhere in Figma files
31
- - Post comments on specific design elements
32
- - Reply to existing comment threads
 
33
 
34
- **🤖 AI-Powered Workflows:**
35
- - Auto-respond to design feedback
36
- - Analyze and summarize design discussions
37
- - Coordinate team reviews efficiently
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
 
39
  ## 🚀 Quick Start
40
 
@@ -68,58 +90,133 @@ Add this configuration to your Claude Desktop config file:
68
 
69
  ## ✨ Features
70
 
71
- - 📝 **Retrieve Comments**: Get all comments from any Figma file with full details
72
- - 👥 **Author Information**: Extract author data, timestamps, and reply threads
73
- - 📊 **Smart Summaries**: Generate statistics by author, location, and resolution status
74
- - 🔧 **Easy Integration**: Works with Claude Desktop and any MCP client
 
 
 
 
 
75
  - 🔒 **Secure**: Each user uses their own Figma token
76
 
77
- ## 🛠️ Available Tools
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
 
79
- ### `get_figma_comments`
80
 
81
- Retrieve all comments from a Figma file with complete information including text, authors, timestamps, locations, and reply threads.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
 
83
- **Parameters:**
84
- - `file_id` (string): The Figma file ID from the URL
85
 
86
- **Example:**
87
  ```
88
- Get all comments from Figma file ABC123XYZ
 
 
89
  ```
90
 
91
- ### `summarize_figma_comments`
92
-
93
- Generate a comprehensive summary and analysis of all comments in a Figma file.
94
-
95
- **Parameters:**
96
- - `file_id` (string): The Figma file ID from the URL
97
-
98
- **Example:**
99
  ```
100
- Summarize the comments from Figma file ABC123XYZ
 
 
101
  ```
102
 
103
- ## 📖 Use Cases
104
-
105
- - **Design Reviews**: Quickly gather all feedback from stakeholders
106
- - **Team Collaboration**: Analyze comment patterns and identify bottlenecks
107
- - **Comment Management**: Track unresolved comments and resolution status
108
- - **Reporting**: Generate summaries for design sprint retrospectives
109
- - **AI Analysis**: Let Claude analyze sentiment and extract action items
110
-
111
- ## 💡 Example Conversations
112
-
113
- Once installed, you can ask Claude:
114
-
115
  ```
116
- "Get all comments from Figma file ABC123 and tell me which ones are unresolved"
117
-
118
- "Summarize the feedback on file XYZ123 and identify common themes"
119
 
120
- "Show me all comments by username 'john' in file ABC123"
 
 
 
 
121
 
122
- "Which design elements in file XYZ have the most comments?"
 
 
 
 
123
  ```
124
 
125
  ## 🔍 Finding Your Figma File ID
 
1
  ---
2
+ title: Figma REST API MCP Server
3
+ emoji: 🎨
4
  colorFrom: purple
5
  colorTo: blue
6
  sdk: static
 
9
  - mcp
10
  - model-context-protocol
11
  - figma
12
+ - api
13
  - design
14
  - collaboration
15
  - claude
16
+ - webhooks
17
+ - variables
18
  license: mit
19
  ---
20
 
21
+ # 🎨 Complete Figma REST API MCP Server
22
 
23
+ A comprehensive [Model Context Protocol](https://modelcontextprotocol.io/) server that gives Claude Desktop **complete access to the Figma REST API**. Access files, comments, variables, webhooks, projects, teams, dev resources, version history, and more - all through natural language.
24
 
25
+ ## 🎯 Complete API Coverage - 29 Endpoints
26
 
27
+ **📖 Files & Content:**
28
+ - Get complete file structure as JSON with all nodes and components
29
+ - Retrieve specific nodes from files
30
+ - Render images from nodes in any format (PNG, JPG, SVG, PDF)
31
+ - Access image fills and assets
32
 
33
+ **💬 Comments & Collaboration:**
34
+ - Read, create, delete comments with full threading support
35
+ - React to comments with emojis
36
+ - Reply to threads with @mentions
37
+ - Track comment authors and resolution status
38
 
39
+ **🎨 Variables & Design Systems (Enterprise):**
40
+ - Access local and published variables
41
+ - Create and update design tokens
42
+ - Manage design system synchronization
43
+
44
+ **🔔 Webhooks & Automation:**
45
+ - Create webhooks for file updates and events
46
+ - Manage webhook lifecycle (get, update, delete)
47
+ - Debug webhook requests
48
+
49
+ **👥 Projects & Teams:**
50
+ - Access team projects and files
51
+ - Navigate organizational structure
52
+
53
+ **🔧 Dev Resources:**
54
+ - Attach developer URLs to design nodes
55
+ - Bridge design and development workflows
56
+
57
+ **📜 Version History:**
58
+ - Access file version history
59
+ - Track design evolution
60
 
61
  ## 🚀 Quick Start
62
 
 
90
 
91
  ## ✨ Features
92
 
93
+ - 📄 **29 Endpoints** covering the complete Figma REST API
94
+ - 💬 **Comments**: Full CRUD + reactions + @mentions (9 endpoints)
95
+ - 📁 **Files**: Complete file access, node retrieval, image rendering (4 endpoints)
96
+ - 🎨 **Variables**: Design tokens management - Enterprise (3 endpoints)
97
+ - 🔔 **Webhooks**: Event-driven automations (6 endpoints)
98
+ - 👥 **Teams & Projects**: Organizational navigation (2 endpoints)
99
+ - 🔧 **Dev Resources**: Design-to-dev bridge (3 endpoints)
100
+ - 📜 **Version History**: Track file changes (1 endpoint)
101
+ - 👤 **Users**: Current user info (1 endpoint)
102
  - 🔒 **Secure**: Each user uses their own Figma token
103
 
104
+ ## 🛠️ Available Tools (29 Total)
105
+
106
+ ### 💬 Comments (9 endpoints)
107
+ - `get_figma_comments` - Retrieve all comments with threads
108
+ - `summarize_figma_comments` - Generate statistics
109
+ - `get_comment_users` - List users for @mentions
110
+ - `post_figma_comment` - Create comments with @mentions
111
+ - `reply_to_figma_comment` - Reply to threads
112
+ - `delete_figma_comment` - Delete comments
113
+ - `get_comment_reactions` - Get emoji reactions
114
+ - `post_comment_reaction` - Add reactions
115
+ - `delete_comment_reaction` - Remove reactions
116
+
117
+ ### 📄 Files (4 endpoints)
118
+ - `get_file` - Get complete file structure as JSON
119
+ - `get_file_nodes` - Get specific nodes by ID
120
+ - `render_images` - Render as PNG/JPG/SVG/PDF
121
+ - `get_image_fills` - Get all image fill URLs
122
+
123
+ ### 👤 Users (1 endpoint)
124
+ - `get_current_user` - Get authenticated user info
125
+
126
+ ### 📜 Version History (1 endpoint)
127
+ - `get_version_history` - Get file version history
128
+
129
+ ### 👥 Projects & Teams (2 endpoints)
130
+ - `get_team_projects` - List team projects
131
+ - `get_project_files` - List project files
132
+
133
+ ### 🎨 Variables (3 endpoints - Enterprise)
134
+ - `get_local_variables` - Get local variables
135
+ - `get_published_variables` - Get published variables
136
+ - `create_variables` - Create/update variables
137
+
138
+ ### 🔧 Dev Resources (3 endpoints)
139
+ - `get_dev_resources` - Get developer URLs
140
+ - `create_dev_resource` - Attach URL to node
141
+ - `delete_dev_resource` - Remove resource
142
+
143
+ ### 🔔 Webhooks V2 (6 endpoints)
144
+ - `create_webhook` - Create webhook for events
145
+ - `get_webhook` - Get webhook by ID
146
+ - `get_webhooks` - List all webhooks
147
+ - `update_webhook` - Update webhook
148
+ - `delete_webhook` - Delete webhook
149
+ - `get_webhook_requests` - Debug webhook requests
150
 
151
+ ## 📖 Use Cases
152
 
153
+ **💬 Comments & Collaboration:**
154
+ - Design reviews and feedback management
155
+ - Automated responses to comment threads
156
+ - Comment sentiment analysis
157
+ - Team collaboration analytics
158
+
159
+ **📁 File Management:**
160
+ - Extract complete design system structure
161
+ - Generate documentation from Figma files
162
+ - Export assets and images programmatically
163
+ - Navigate file hierarchies
164
+
165
+ **🎨 Design Systems (Enterprise):**
166
+ - Synchronize design tokens across files
167
+ - Track variable changes
168
+ - Automate design system documentation
169
+ - Validate token usage
170
+
171
+ **🔔 Automation:**
172
+ - Set up file update notifications
173
+ - Trigger workflows on design changes
174
+ - Monitor library publications
175
+ - Track team activity
176
+
177
+ **👥 Team Organization:**
178
+ - Audit project file organization
179
+ - Generate team activity reports
180
+ - Navigate organizational structures
181
+
182
+ **🔧 Developer Handoff:**
183
+ - Attach implementation URLs to designs
184
+ - Bridge design-to-development workflow
185
+ - Track component implementation status
186
 
187
+ ## 💡 Example Conversations
 
188
 
189
+ **💬 Comments:**
190
  ```
191
+ "Get all comments from file ABC123 and reply to unresolved ones"
192
+ "Delete comment 789 and add a 👍 reaction to comment 456"
193
+ "Post a comment mentioning @john_designer to review colors"
194
  ```
195
 
196
+ **📁 Files & Nodes:**
 
 
 
 
 
 
 
197
  ```
198
+ "Get the complete structure of file ABC123"
199
+ "Render nodes 1:5,1:6,1:7 as PNG at 2x scale"
200
+ "Export node 123:456 as SVG"
201
  ```
202
 
203
+ **🎨 Variables:**
204
+ ```
205
+ "Get all local variables from file ABC123"
206
+ "Create a new color variable named 'primary-blue'"
 
 
 
 
 
 
 
 
207
  ```
 
 
 
208
 
209
+ **🔔 Webhooks:**
210
+ ```
211
+ "Create a webhook for FILE_UPDATE events on team T123"
212
+ "List all webhooks and show request history"
213
+ ```
214
 
215
+ **Combined Workflows:**
216
+ ```
217
+ "Get file structure, find all buttons, render as images, create catalog"
218
+ "Analyze comments, group by theme, post summary with @mentions"
219
+ "Set up webhook for updates, document all current variables"
220
  ```
221
 
222
  ## 🔍 Finding Your Figma File ID
README.md CHANGED
@@ -1,42 +1,63 @@
1
  # mcp-figma-comment-summary
2
 
3
- > MCP server to retrieve, analyze, and create Figma file comments with Claude Desktop
4
 
5
  [![npm version](https://img.shields.io/npm/v/mcp-figma-comment-summary.svg)](https://www.npmjs.com/package/mcp-figma-comment-summary)
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
 
8
- A [Model Context Protocol](https://modelcontextprotocol.io/) server that enables Claude Desktop to retrieve, analyze, and create comments in Figma files. Perfect for design review workflows, team collaboration analysis, and comment management.
9
 
10
  ## 🎯 What Can This Do?
11
 
12
- This MCP gives Claude **full read and write access** to Figma comments:
13
 
14
- **📖 Reading:**
15
- - Get all comments from any Figma file
16
- - View authors, timestamps, locations, and reply threads
17
- - Generate automatic summaries and statistics
 
18
 
19
- **✍️ Writing:**
20
- - Create new comments anywhere in a Figma file
21
- - Post comments on specific design elements (nodes)
22
- - Reply to existing comments to maintain conversations
23
- - Let Claude intelligently respond to feedback
24
 
25
- **🤖 AI-Powered Workflows:**
26
- - Analyze designs and post constructive feedback
27
- - Automatically respond to comment threads
28
- - Summarize feedback for team meetings
29
- - Coordinate reviews across multiple stakeholders
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
 
31
  ## Features
32
 
33
- - 📝 Retrieve all comments from any Figma file with full details
34
- - ✍️ Create new comments and reply to existing ones
35
- - 👥 Extract author information, timestamps, and reply threads
36
- - 📊 Generate automatic summaries with statistics by author, location, and resolution status
37
- - 📍 Post comments to specific locations (nodes or coordinates)
38
- - 🔧 Easy integration with Claude Desktop and any MCP client
39
- - 🔒 Each user uses their own Figma token (secure and private)
 
 
40
 
41
  ## Quick Start
42
 
@@ -83,15 +104,76 @@ After updating the configuration, completely quit and restart Claude Desktop to
83
 
84
  ## Available Tools
85
 
86
- This MCP provides **5 tools** split into **read** and **write** operations:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
 
88
- | Tool | Type | Description |
89
- |------|------|-------------|
90
- | `get_figma_comments` | 📖 Read | Retrieve all comments with full details |
91
- | `summarize_figma_comments` | 📖 Read | Generate statistics and analysis |
92
- | `get_comment_users` | 📖 Read | List users who have commented (useful for @mentions) |
93
- | `post_figma_comment` | ✍️ Write | Create new comments with optional @mentions |
94
- | `reply_to_figma_comment` | ✍️ Write | Reply to comments with optional @mentions |
 
 
 
95
 
96
  ---
97
 
@@ -313,61 +395,107 @@ Claude will:
313
 
314
  ## Use Cases
315
 
316
- - **Design Reviews:** Quickly gather all feedback and comments from stakeholders
317
- - **Automated Feedback:** Let Claude post comments based on design analysis
318
- - **Team Collaboration:** Analyze comment patterns and identify bottlenecks
319
- - **Comment Management:** Find unresolved comments and track resolution status
320
- - **Batch Responses:** Reply to multiple comments efficiently with AI assistance
321
- - **Reporting:** Generate summaries for design sprint retrospectives
322
- - **AI Analysis:** Let Claude analyze comment sentiment and extract action items
323
- - **Proactive Commenting:** Claude can suggest and post improvement suggestions
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
324
 
325
  ## Example Conversations with Claude
326
 
327
  Once installed, you can ask Claude:
328
 
329
- **Reading comments:**
330
  ```
331
  "Get all comments from Figma file ABC123 and tell me which ones are unresolved"
332
-
333
- "Summarize the feedback on file XYZ123 and identify common themes"
334
-
335
- "Show me all comments by username 'john' in file ABC123"
336
-
337
- "Which design elements in file XYZ have the most comments?"
338
  ```
339
 
340
- **Writing comments:**
341
  ```
342
- "Post a comment on Figma file ABC123 saying 'Approved for development'"
343
-
344
- "Reply to all unresolved comments in file XYZ123 asking for clarification"
345
-
346
- "Add a comment to node 123:456 in file ABC: 'This button needs better contrast'"
347
-
348
- "Review all comments in file ABC123 and respond to the ones that need attention"
349
  ```
350
 
351
- **Using @mentions:**
 
 
 
 
352
  ```
353
- "Show me who has commented on file ABC123"
354
-
355
- "Post a comment mentioning John: '@john_designer Please review the accessibility updates'"
356
-
357
- "Reply to comment 456 and mention Maria: '@maria_ux What are your thoughts on this?'"
358
 
359
- "Get all comments from Sarah and reply to each one mentioning her"
 
 
 
 
360
 
361
- "Post a summary comment mentioning all users who commented: 'Thanks @john @maria @sarah for the feedback'"
 
 
 
 
362
  ```
363
 
364
- **Combined workflows:**
 
 
 
 
 
365
  ```
366
- "Get all unresolved comments from file ABC123, analyze them, and post a summary comment"
367
 
368
- "Find comments mentioning 'urgent' and reply with 'Working on this today'"
 
 
 
 
 
 
 
369
 
370
- "Get user list, then reply to all comments from John mentioning him in each response"
 
 
 
 
 
371
  ```
372
 
373
  ## Verification
@@ -377,12 +505,15 @@ To verify the installation:
377
  1. Open Claude Desktop
378
  2. Look for the tools icon (🔧) in the interface
379
  3. You should see "figma-comments" listed as an available server
380
- 4. The following **5 tools** should appear:
381
- - `get_figma_comments` (Read)
382
- - `summarize_figma_comments` (Read)
383
- - `get_comment_users` (Read - for @mentions)
384
- - `post_figma_comment` (Write - supports @mentions)
385
- - `reply_to_figma_comment` (Write - supports @mentions)
 
 
 
386
 
387
  ## Troubleshooting
388
 
 
1
  # mcp-figma-comment-summary
2
 
3
+ > Complete MCP server for Figma REST API - Full access to files, comments, variables, webhooks, projects, and more
4
 
5
  [![npm version](https://img.shields.io/npm/v/mcp-figma-comment-summary.svg)](https://www.npmjs.com/package/mcp-figma-comment-summary)
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
 
8
+ A comprehensive [Model Context Protocol](https://modelcontextprotocol.io/) server that gives Claude Desktop **complete access to the Figma REST API**. Access files, comments, variables, webhooks, projects, teams, dev resources, version history, and more - all through natural language.
9
 
10
  ## 🎯 What Can This Do?
11
 
12
+ This MCP gives Claude **complete access to Figma's REST API with 29 endpoints**:
13
 
14
+ **📖 Files & Content:**
15
+ - Get complete file structure as JSON with all nodes and components
16
+ - Retrieve specific nodes from files
17
+ - Render images from nodes in any format (PNG, JPG, SVG, PDF)
18
+ - Access image fills and assets
19
 
20
+ **💬 Comments & Collaboration:**
21
+ - Read, create, delete comments with full threading support
22
+ - React to comments with emojis
23
+ - Reply to threads with @mentions
24
+ - Track comment authors and resolution status
25
 
26
+ **🎨 Variables & Design Systems (Enterprise):**
27
+ - Access local and published variables
28
+ - Create and update design tokens
29
+ - Manage design system synchronization
30
+
31
+ **🔔 Webhooks & Automation:**
32
+ - Create webhooks for file updates and events
33
+ - Manage webhook lifecycle (get, update, delete)
34
+ - Debug webhook requests
35
+
36
+ **👥 Projects & Teams:**
37
+ - Access team projects and files
38
+ - Navigate organizational structure
39
+ - Manage project resources
40
+
41
+ **🔧 Dev Resources:**
42
+ - Attach developer URLs to design nodes
43
+ - Bridge design and development workflows
44
+ - Track component implementations
45
+
46
+ **📜 Version History:**
47
+ - Access file version history
48
+ - Track design evolution
49
 
50
  ## Features
51
 
52
+ - 📄 **29 Endpoints** covering the complete Figma REST API
53
+ - 💬 **Comments**: Full CRUD + reactions + @mentions
54
+ - 📁 **Files**: Complete file access, node retrieval, image rendering
55
+ - 🎨 **Variables**: Design tokens management (Enterprise)
56
+ - 🔔 **Webhooks**: Event-driven automations
57
+ - 👥 **Teams & Projects**: Organizational navigation
58
+ - 🔧 **Dev Resources**: Design-to-dev bridge
59
+ - 📜 **Version History**: Track file changes
60
+ - 🔒 **Secure**: Each user uses their own Figma token
61
 
62
  ## Quick Start
63
 
 
104
 
105
  ## Available Tools
106
 
107
+ This MCP provides **29 tools** covering the complete Figma REST API:
108
+
109
+ ### 💬 Comments (9 endpoints)
110
+
111
+ | Tool | Method | Description |
112
+ |------|--------|-------------|
113
+ | `get_figma_comments` | GET | Retrieve all comments with full details, threads, and metadata |
114
+ | `summarize_figma_comments` | GET | Generate statistics by author, location, and status |
115
+ | `get_comment_users` | GET | List users for @mentions |
116
+ | `post_figma_comment` | POST | Create comments with @mentions and location |
117
+ | `reply_to_figma_comment` | POST | Reply to comment threads with @mentions |
118
+ | `delete_figma_comment` | DELETE | Delete comments |
119
+ | `get_comment_reactions` | GET | Get emoji reactions on comments |
120
+ | `post_comment_reaction` | POST | Add emoji reactions |
121
+ | `delete_comment_reaction` | DELETE | Remove emoji reactions |
122
+
123
+ ### 📄 Files (4 endpoints)
124
+
125
+ | Tool | Method | Description |
126
+ |------|--------|-------------|
127
+ | `get_file` | GET | Get complete file structure as JSON |
128
+ | `get_file_nodes` | GET | Get specific nodes by ID |
129
+ | `render_images` | GET | Render nodes as images (PNG, JPG, SVG, PDF) |
130
+ | `get_image_fills` | GET | Get all image fill URLs from document |
131
+
132
+ ### 👤 Users (1 endpoint)
133
+
134
+ | Tool | Method | Description |
135
+ |------|--------|-------------|
136
+ | `get_current_user` | GET | Get authenticated user information |
137
+
138
+ ### 📜 Version History (1 endpoint)
139
+
140
+ | Tool | Method | Description |
141
+ |------|--------|-------------|
142
+ | `get_version_history` | GET | Get file version history |
143
+
144
+ ### 👥 Projects & Teams (2 endpoints)
145
+
146
+ | Tool | Method | Description |
147
+ |------|--------|-------------|
148
+ | `get_team_projects` | GET | List all projects in a team |
149
+ | `get_project_files` | GET | List all files in a project |
150
+
151
+ ### 🎨 Variables (3 endpoints - Enterprise)
152
+
153
+ | Tool | Method | Description |
154
+ |------|--------|-------------|
155
+ | `get_local_variables` | GET | Get local variables from file |
156
+ | `get_published_variables` | GET | Get published variables |
157
+ | `create_variables` | POST | Create/update variables |
158
+
159
+ ### 🔧 Dev Resources (3 endpoints)
160
+
161
+ | Tool | Method | Description |
162
+ |------|--------|-------------|
163
+ | `get_dev_resources` | GET | Get developer URLs attached to nodes |
164
+ | `create_dev_resource` | POST | Attach developer URL to node |
165
+ | `delete_dev_resource` | DELETE | Remove developer resource |
166
 
167
+ ### 🔔 Webhooks V2 (6 endpoints)
168
+
169
+ | Tool | Method | Description |
170
+ |------|--------|-------------|
171
+ | `create_webhook` | POST | Create webhook for events |
172
+ | `get_webhook` | GET | Get webhook by ID |
173
+ | `get_webhooks` | GET | List all webhooks |
174
+ | `update_webhook` | PUT | Update webhook properties |
175
+ | `delete_webhook` | DELETE | Delete webhook |
176
+ | `get_webhook_requests` | GET | Get webhook request history (debugging) |
177
 
178
  ---
179
 
 
395
 
396
  ## Use Cases
397
 
398
+ **💬 Comments & Collaboration:**
399
+ - Design reviews and feedback management
400
+ - Automated responses to comment threads
401
+ - Comment sentiment analysis and action item extraction
402
+ - Team collaboration analytics
403
+
404
+ **📁 File Management:**
405
+ - Extract complete design system structure
406
+ - Generate documentation from Figma files
407
+ - Export assets and images programmatically
408
+ - Navigate file hierarchies
409
+
410
+ **🎨 Design Systems (Enterprise):**
411
+ - Synchronize design tokens across files
412
+ - Track variable changes and updates
413
+ - Automate design system documentation
414
+ - Validate token usage
415
+
416
+ **🔔 Automation:**
417
+ - Set up file update notifications
418
+ - Trigger workflows on design changes
419
+ - Monitor library publications
420
+ - Track team activity
421
+
422
+ **👥 Team & Project Organization:**
423
+ - Audit project file organization
424
+ - Generate team activity reports
425
+ - Navigate organizational structures
426
+ - Track file ownership
427
+
428
+ **🔧 Developer Handoff:**
429
+ - Attach implementation URLs to designs
430
+ - Bridge design-to-development workflow
431
+ - Track component implementation status
432
+ - Document design specifications
433
 
434
  ## Example Conversations with Claude
435
 
436
  Once installed, you can ask Claude:
437
 
438
+ **💬 Comments:**
439
  ```
440
  "Get all comments from Figma file ABC123 and tell me which ones are unresolved"
441
+ "Reply to all unresolved comments asking for clarification"
442
+ "Delete comment 789 from file ABC123"
443
+ "Add a 👍 reaction to comment 456"
444
+ "Post a comment mentioning @john_designer to review the colors"
 
 
445
  ```
446
 
447
+ **📁 Files & Nodes:**
448
  ```
449
+ "Get the complete structure of Figma file ABC123"
450
+ "Show me node 123:456 from file ABC123"
451
+ "Render nodes 1:5,1:6,1:7 as PNG images at 2x scale"
452
+ "Export node 123:456 as SVG"
453
+ "Get all image fills from file ABC123"
 
 
454
  ```
455
 
456
+ **👤 Users & Teams:**
457
+ ```
458
+ "Who am I logged in as?"
459
+ "Show me all projects in team T123"
460
+ "List all files in project P456"
461
  ```
 
 
 
 
 
462
 
463
+ **📜 Version History:**
464
+ ```
465
+ "Show me the version history of file ABC123"
466
+ "What changed in the last 3 versions?"
467
+ ```
468
 
469
+ **🎨 Variables (Enterprise):**
470
+ ```
471
+ "Get all local variables from file ABC123"
472
+ "Show me published variables"
473
+ "Create a new color variable named 'primary-blue'"
474
  ```
475
 
476
+ **🔧 Dev Resources:**
477
+ ```
478
+ "Get all dev resources from file ABC123"
479
+ "Attach Storybook URL to node 123:456"
480
+ "Add implementation link https://... to node 1:5"
481
+ "Delete dev resource DR789"
482
  ```
 
483
 
484
+ **🔔 Webhooks:**
485
+ ```
486
+ "Create a webhook for FILE_UPDATE events on team T123"
487
+ "List all webhooks for my team"
488
+ "Show me webhook requests for debugging"
489
+ "Update webhook W456 to PAUSED status"
490
+ "Delete webhook W789"
491
+ ```
492
 
493
+ **Combined Workflows:**
494
+ ```
495
+ "Get file structure, find all button components, render them as images, and create a visual catalog"
496
+ "Analyze all comments, group by theme, and post a summary with @mentions"
497
+ "Set up a webhook for file updates, then document all current variables"
498
+ "Export all version history, compare changes, and highlight major updates"
499
  ```
500
 
501
  ## Verification
 
505
  1. Open Claude Desktop
506
  2. Look for the tools icon (🔧) in the interface
507
  3. You should see "figma-comments" listed as an available server
508
+ 4. All **29 tools** should be available across these categories:
509
+ - **Comments** (9 tools): get, create, delete, react, reply, summarize, users
510
+ - **Files** (4 tools): get file, get nodes, render images, get image fills
511
+ - **Users** (1 tool): get current user
512
+ - **Version History** (1 tool): get versions
513
+ - **Projects & Teams** (2 tools): get projects, get files
514
+ - **Variables** (3 tools): get local, get published, create
515
+ - **Dev Resources** (3 tools): get, create, delete
516
+ - **Webhooks** (6 tools): create, get, list, update, delete, debug
517
 
518
  ## Troubleshooting
519
 
index.js CHANGED
@@ -294,297 +294,1949 @@ async function replyToFigmaComment(fileId, commentId, message) {
294
  }
295
 
296
  /**
297
- * Generate a summary of comments
298
- * @param {Array} comments - Array of comment objects
299
- * @returns {Object} Summary statistics and structured data
 
300
  */
301
- function summarizeComments(comments) {
302
- const totalComments = comments.length;
303
- const totalReplies = comments.reduce((sum, c) => sum + c.replies.length, 0);
304
- const resolvedComments = comments.filter(c => c.resolvedAt).length;
305
- const unresolvedComments = totalComments - resolvedComments;
 
 
 
306
 
307
- // Group by author
308
- const authorStats = {};
309
- comments.forEach(comment => {
310
- const handle = comment.author.handle;
311
- if (!authorStats[handle]) {
312
- authorStats[handle] = { comments: 0, replies: 0 };
313
  }
314
- authorStats[handle].comments++;
315
 
316
- comment.replies.forEach(reply => {
317
- const replyHandle = reply.author.handle;
318
- if (!authorStats[replyHandle]) {
319
- authorStats[replyHandle] = { comments: 0, replies: 0 };
320
- }
321
- authorStats[replyHandle].replies++;
 
 
 
 
 
 
 
 
 
 
 
 
322
  });
323
- });
324
 
325
- // Group by location (node)
326
- const locationStats = {};
327
- comments.forEach(comment => {
328
- if (comment.location && comment.location.node_id) {
329
- const nodeId = comment.location.node_id;
330
- locationStats[nodeId] = (locationStats[nodeId] || 0) + 1;
331
  }
332
- });
333
 
334
- return {
335
- summary: {
336
- total_comments: totalComments,
337
- total_replies: totalReplies,
338
- resolved: resolvedComments,
339
- unresolved: unresolvedComments,
340
- },
341
- by_author: authorStats,
342
- by_location: locationStats,
343
- comments: comments.map(c => ({
344
- id: c.id,
345
- preview: c.message.substring(0, 100) + (c.message.length > 100 ? '...' : ''),
346
- author: c.author.handle,
347
- timestamp: c.timestamp,
348
- replies_count: c.replies.length,
349
- resolved: !!c.resolvedAt,
350
- location: c.location,
351
- })),
352
- };
353
  }
354
 
355
- // Create MCP server
356
- const server = new Server(
357
- {
358
- name: 'mcp-figma-comment-summary',
359
- version: '1.2.1',
360
- },
361
- {
362
- capabilities: {
363
- tools: {},
364
- },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
365
  }
366
- );
367
 
368
- // Define available tools
369
- server.setRequestHandler(ListToolsRequestSchema, async () => {
370
- return {
371
- tools: [
372
- {
373
- name: 'get_figma_comments',
374
- description: 'Retrieve all comments from a Figma file, including comment text, author info, timestamps, location, and reply threads',
375
- inputSchema: {
376
- type: 'object',
377
- properties: {
378
- file_id: {
379
- type: 'string',
380
- description: 'The Figma file ID (from the URL: figma.com/file/FILE_ID/...)',
381
- },
382
- },
383
- required: ['file_id'],
384
- },
385
  },
386
- {
387
- name: 'summarize_figma_comments',
388
- description: 'Get a summary and analysis of all comments in a Figma file, including statistics by author, location, and resolution status',
389
- inputSchema: {
390
- type: 'object',
391
- properties: {
392
- file_id: {
393
- type: 'string',
394
- description: 'The Figma file ID (from the URL: figma.com/file/FILE_ID/...)',
395
- },
396
- },
397
- required: ['file_id'],
398
- },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
399
  },
400
- {
401
- name: 'post_figma_comment',
402
- description: 'Create a new comment on a Figma file. Optionally specify a location (node_id and/or coordinates) where the comment should be placed',
403
- inputSchema: {
404
- type: 'object',
405
- properties: {
406
- file_id: {
407
- type: 'string',
408
- description: 'The Figma file ID (from the URL: figma.com/file/FILE_ID/...)',
409
- },
410
- message: {
411
- type: 'string',
412
- description: 'The text content of the comment. You can mention users using @username format (e.g., "Hey @john, please review this")',
413
- },
414
- node_id: {
415
- type: 'string',
416
- description: 'Optional: The ID of the node/element to attach the comment to',
417
- },
418
- x: {
419
- type: 'number',
420
- description: 'Optional: X coordinate for the comment position',
421
- },
422
- y: {
423
- type: 'number',
424
- description: 'Optional: Y coordinate for the comment position',
425
- },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
426
  },
427
- required: ['file_id', 'message'],
428
- },
429
- },
430
- {
431
- name: 'reply_to_figma_comment',
432
- description: 'Reply to an existing comment in a Figma file',
433
- inputSchema: {
434
- type: 'object',
435
- properties: {
436
- file_id: {
437
- type: 'string',
438
- description: 'The Figma file ID (from the URL: figma.com/file/FILE_ID/...)',
439
- },
440
- comment_id: {
441
- type: 'string',
442
- description: 'The ID of the comment to reply to',
443
- },
444
- message: {
445
- type: 'string',
446
- description: 'The text content of the reply. You can mention users using @username format (e.g., "@maria I agree with your point")',
447
- },
448
  },
449
- required: ['file_id', 'comment_id', 'message'],
450
- },
451
- },
452
- {
453
- name: 'get_comment_users',
454
- description: 'Get a list of all users who have commented on a Figma file. Useful to see who can be mentioned in comments using @username',
455
- inputSchema: {
456
- type: 'object',
457
- properties: {
458
- file_id: {
459
- type: 'string',
460
- description: 'The Figma file ID (from the URL: figma.com/file/FILE_ID/...)',
461
- },
462
  },
463
- required: ['file_id'],
464
- },
465
- },
466
- ],
467
- };
468
- });
469
 
470
- // Handle tool calls
471
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
472
- const { name, arguments: args } = request.params;
473
 
474
- try {
475
- if (name === 'get_figma_comments') {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
476
  const { file_id } = args;
477
 
478
  if (!file_id) {
479
  throw new Error('file_id is required');
480
  }
481
 
482
- const comments = await getFigmaComments(file_id);
483
 
484
  return {
485
  content: [
486
  {
487
  type: 'text',
488
- text: JSON.stringify(comments, null, 2),
489
  },
490
  ],
491
  };
492
  }
493
 
494
- if (name === 'summarize_figma_comments') {
495
  const { file_id } = args;
496
 
497
  if (!file_id) {
498
  throw new Error('file_id is required');
499
  }
500
 
501
- const comments = await getFigmaComments(file_id);
502
- const summary = summarizeComments(comments);
503
 
504
  return {
505
  content: [
506
  {
507
  type: 'text',
508
- text: JSON.stringify(summary, null, 2),
509
  },
510
  ],
511
  };
512
  }
513
 
514
- if (name === 'post_figma_comment') {
515
- const { file_id, message, node_id, x, y } = args;
516
 
517
  if (!file_id) {
518
  throw new Error('file_id is required');
519
  }
520
 
521
- if (!message) {
522
- throw new Error('message is required');
523
  }
524
 
525
- // Build location object if any location parameters provided
526
- const location = {};
527
- if (node_id) location.node_id = node_id;
528
- if (x !== undefined) location.x = x;
529
- if (y !== undefined) location.y = y;
530
 
531
- const result = await postFigmaComment(
532
- file_id,
533
- message,
534
- Object.keys(location).length > 0 ? location : null
535
- );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
536
 
537
  return {
538
  content: [
539
  {
540
  type: 'text',
541
- text: `Comment posted successfully!\n\n${JSON.stringify(result, null, 2)}`,
542
  },
543
  ],
544
  };
545
  }
546
 
547
- if (name === 'reply_to_figma_comment') {
548
- const { file_id, comment_id, message } = args;
549
 
550
  if (!file_id) {
551
  throw new Error('file_id is required');
552
  }
553
 
554
- if (!comment_id) {
555
- throw new Error('comment_id is required');
556
  }
557
 
558
- if (!message) {
559
- throw new Error('message is required');
560
  }
561
 
562
- const result = await replyToFigmaComment(file_id, comment_id, message);
 
 
 
 
 
 
 
 
 
 
563
 
564
  return {
565
  content: [
566
  {
567
  type: 'text',
568
- text: `Reply posted successfully!\n\n${JSON.stringify(result, null, 2)}`,
569
  },
570
  ],
571
  };
572
  }
573
 
574
- if (name === 'get_comment_users') {
575
- const { file_id } = args;
576
 
577
  if (!file_id) {
578
  throw new Error('file_id is required');
579
  }
580
 
581
- const { users } = await getCommentUsers(file_id);
 
 
 
 
582
 
583
  return {
584
  content: [
585
  {
586
  type: 'text',
587
- text: `Found ${users.length} user(s) who have commented:\n\n${JSON.stringify(users, null, 2)}\n\nYou can mention these users in comments using @${users.map(u => u.handle).join(', @')}`,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
588
  },
589
  ],
590
  };
 
294
  }
295
 
296
  /**
297
+ * Delete a comment from a Figma file
298
+ * @param {string} fileId - The Figma file ID
299
+ * @param {string} commentId - The ID of the comment to delete
300
+ * @returns {Promise<Object>} The deletion response
301
  */
302
+ async function deleteFigmaComment(fileId, commentId) {
303
+ try {
304
+ const response = await fetch(`${FIGMA_API_BASE}/files/${fileId}/comments/${commentId}`, {
305
+ method: 'DELETE',
306
+ headers: {
307
+ 'X-Figma-Token': FIGMA_TOKEN,
308
+ },
309
+ });
310
 
311
+ if (!response.ok) {
312
+ throw new Error(`Figma API error: ${response.status} ${response.statusText}`);
 
 
 
 
313
  }
 
314
 
315
+ return { success: true, message: 'Comment deleted successfully' };
316
+ } catch (error) {
317
+ throw new Error(`Failed to delete Figma comment: ${error.message}`);
318
+ }
319
+ }
320
+
321
+ /**
322
+ * Get reactions for a comment
323
+ * @param {string} fileId - The Figma file ID
324
+ * @param {string} commentId - The ID of the comment
325
+ * @returns {Promise<Object>} The reactions data
326
+ */
327
+ async function getCommentReactions(fileId, commentId) {
328
+ try {
329
+ const response = await fetch(`${FIGMA_API_BASE}/files/${fileId}/comments/${commentId}/reactions`, {
330
+ headers: {
331
+ 'X-Figma-Token': FIGMA_TOKEN,
332
+ },
333
  });
 
334
 
335
+ if (!response.ok) {
336
+ throw new Error(`Figma API error: ${response.status} ${response.statusText}`);
 
 
 
 
337
  }
 
338
 
339
+ const data = await response.json();
340
+ return data;
341
+ } catch (error) {
342
+ throw new Error(`Failed to get comment reactions: ${error.message}`);
343
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
344
  }
345
 
346
+ /**
347
+ * Post a reaction to a comment
348
+ * @param {string} fileId - The Figma file ID
349
+ * @param {string} commentId - The ID of the comment
350
+ * @param {string} emoji - The emoji reaction
351
+ * @returns {Promise<Object>} The created reaction
352
+ */
353
+ async function postCommentReaction(fileId, commentId, emoji) {
354
+ try {
355
+ const response = await fetch(`${FIGMA_API_BASE}/files/${fileId}/comments/${commentId}/reactions`, {
356
+ method: 'POST',
357
+ headers: {
358
+ 'X-Figma-Token': FIGMA_TOKEN,
359
+ 'Content-Type': 'application/json',
360
+ },
361
+ body: JSON.stringify({ emoji }),
362
+ });
363
+
364
+ if (!response.ok) {
365
+ const errorData = await response.json().catch(() => ({}));
366
+ throw new Error(`Figma API error: ${response.status} ${response.statusText} - ${errorData.message || ''}`);
367
+ }
368
+
369
+ const data = await response.json();
370
+ return data;
371
+ } catch (error) {
372
+ throw new Error(`Failed to post comment reaction: ${error.message}`);
373
  }
374
+ }
375
 
376
+ /**
377
+ * Delete a reaction from a comment
378
+ * @param {string} fileId - The Figma file ID
379
+ * @param {string} commentId - The ID of the comment
380
+ * @param {string} emoji - The emoji reaction to delete
381
+ * @returns {Promise<Object>} The deletion response
382
+ */
383
+ async function deleteCommentReaction(fileId, commentId, emoji) {
384
+ try {
385
+ const response = await fetch(`${FIGMA_API_BASE}/files/${fileId}/comments/${commentId}/reactions?emoji=${encodeURIComponent(emoji)}`, {
386
+ method: 'DELETE',
387
+ headers: {
388
+ 'X-Figma-Token': FIGMA_TOKEN,
 
 
 
 
389
  },
390
+ });
391
+
392
+ if (!response.ok) {
393
+ throw new Error(`Figma API error: ${response.status} ${response.statusText}`);
394
+ }
395
+
396
+ return { success: true, message: 'Reaction deleted successfully' };
397
+ } catch (error) {
398
+ throw new Error(`Failed to delete comment reaction: ${error.message}`);
399
+ }
400
+ }
401
+
402
+ /**
403
+ * Get a Figma file as JSON
404
+ * @param {string} fileId - The Figma file ID
405
+ * @param {Object} options - Optional parameters (version, ids, depth, geometry)
406
+ * @returns {Promise<Object>} The file data
407
+ */
408
+ async function getFile(fileId, options = {}) {
409
+ try {
410
+ const params = new URLSearchParams();
411
+ if (options.version) params.append('version', options.version);
412
+ if (options.ids) params.append('ids', options.ids);
413
+ if (options.depth) params.append('depth', options.depth);
414
+ if (options.geometry) params.append('geometry', options.geometry);
415
+
416
+ const queryString = params.toString();
417
+ const url = `${FIGMA_API_BASE}/files/${fileId}${queryString ? '?' + queryString : ''}`;
418
+
419
+ const response = await fetch(url, {
420
+ headers: {
421
+ 'X-Figma-Token': FIGMA_TOKEN,
422
  },
423
+ });
424
+
425
+ if (!response.ok) {
426
+ throw new Error(`Figma API error: ${response.status} ${response.statusText}`);
427
+ }
428
+
429
+ const data = await response.json();
430
+ return data;
431
+ } catch (error) {
432
+ throw new Error(`Failed to get Figma file: ${error.message}`);
433
+ }
434
+ }
435
+
436
+ /**
437
+ * Get specific nodes from a Figma file
438
+ * @param {string} fileId - The Figma file ID
439
+ * @param {string} ids - Comma-separated list of node IDs
440
+ * @returns {Promise<Object>} The nodes data
441
+ */
442
+ async function getFileNodes(fileId, ids) {
443
+ try {
444
+ const response = await fetch(`${FIGMA_API_BASE}/files/${fileId}/nodes?ids=${encodeURIComponent(ids)}`, {
445
+ headers: {
446
+ 'X-Figma-Token': FIGMA_TOKEN,
447
+ },
448
+ });
449
+
450
+ if (!response.ok) {
451
+ throw new Error(`Figma API error: ${response.status} ${response.statusText}`);
452
+ }
453
+
454
+ const data = await response.json();
455
+ return data;
456
+ } catch (error) {
457
+ throw new Error(`Failed to get file nodes: ${error.message}`);
458
+ }
459
+ }
460
+
461
+ /**
462
+ * Render images from file nodes
463
+ * @param {string} fileId - The Figma file ID
464
+ * @param {Object} options - Options (ids, scale, format, svg_include_id, svg_simplify_stroke, use_absolute_bounds)
465
+ * @returns {Promise<Object>} The rendered images URLs
466
+ */
467
+ async function renderImages(fileId, options = {}) {
468
+ try {
469
+ const params = new URLSearchParams();
470
+ if (options.ids) params.append('ids', options.ids);
471
+ if (options.scale) params.append('scale', options.scale);
472
+ if (options.format) params.append('format', options.format);
473
+ if (options.svg_include_id !== undefined) params.append('svg_include_id', options.svg_include_id);
474
+ if (options.svg_simplify_stroke !== undefined) params.append('svg_simplify_stroke', options.svg_simplify_stroke);
475
+ if (options.use_absolute_bounds !== undefined) params.append('use_absolute_bounds', options.use_absolute_bounds);
476
+
477
+ const queryString = params.toString();
478
+ const url = `${FIGMA_API_BASE}/images/${fileId}${queryString ? '?' + queryString : ''}`;
479
+
480
+ const response = await fetch(url, {
481
+ headers: {
482
+ 'X-Figma-Token': FIGMA_TOKEN,
483
+ },
484
+ });
485
+
486
+ if (!response.ok) {
487
+ throw new Error(`Figma API error: ${response.status} ${response.statusText}`);
488
+ }
489
+
490
+ const data = await response.json();
491
+ return data;
492
+ } catch (error) {
493
+ throw new Error(`Failed to render images: ${error.message}`);
494
+ }
495
+ }
496
+
497
+ /**
498
+ * Get image fills from a Figma file
499
+ * @param {string} fileId - The Figma file ID
500
+ * @returns {Promise<Object>} The image fills data
501
+ */
502
+ async function getImageFills(fileId) {
503
+ try {
504
+ const response = await fetch(`${FIGMA_API_BASE}/files/${fileId}/images`, {
505
+ headers: {
506
+ 'X-Figma-Token': FIGMA_TOKEN,
507
+ },
508
+ });
509
+
510
+ if (!response.ok) {
511
+ throw new Error(`Figma API error: ${response.status} ${response.statusText}`);
512
+ }
513
+
514
+ const data = await response.json();
515
+ return data;
516
+ } catch (error) {
517
+ throw new Error(`Failed to get image fills: ${error.message}`);
518
+ }
519
+ }
520
+
521
+ /**
522
+ * Get current user information
523
+ * @returns {Promise<Object>} The user data
524
+ */
525
+ async function getCurrentUser() {
526
+ try {
527
+ const response = await fetch(`${FIGMA_API_BASE}/me`, {
528
+ headers: {
529
+ 'X-Figma-Token': FIGMA_TOKEN,
530
+ },
531
+ });
532
+
533
+ if (!response.ok) {
534
+ throw new Error(`Figma API error: ${response.status} ${response.statusText}`);
535
+ }
536
+
537
+ const data = await response.json();
538
+ return data;
539
+ } catch (error) {
540
+ throw new Error(`Failed to get current user: ${error.message}`);
541
+ }
542
+ }
543
+
544
+ /**
545
+ * Get version history of a Figma file
546
+ * @param {string} fileId - The Figma file ID
547
+ * @returns {Promise<Object>} The version history data
548
+ */
549
+ async function getVersionHistory(fileId) {
550
+ try {
551
+ const response = await fetch(`${FIGMA_API_BASE}/files/${fileId}/versions`, {
552
+ headers: {
553
+ 'X-Figma-Token': FIGMA_TOKEN,
554
+ },
555
+ });
556
+
557
+ if (!response.ok) {
558
+ throw new Error(`Figma API error: ${response.status} ${response.statusText}`);
559
+ }
560
+
561
+ const data = await response.json();
562
+ return data;
563
+ } catch (error) {
564
+ throw new Error(`Failed to get version history: ${error.message}`);
565
+ }
566
+ }
567
+
568
+ /**
569
+ * Get team projects
570
+ * @param {string} teamId - The team ID
571
+ * @returns {Promise<Object>} The team projects data
572
+ */
573
+ async function getTeamProjects(teamId) {
574
+ try {
575
+ const response = await fetch(`${FIGMA_API_BASE}/teams/${teamId}/projects`, {
576
+ headers: {
577
+ 'X-Figma-Token': FIGMA_TOKEN,
578
+ },
579
+ });
580
+
581
+ if (!response.ok) {
582
+ throw new Error(`Figma API error: ${response.status} ${response.statusText}`);
583
+ }
584
+
585
+ const data = await response.json();
586
+ return data;
587
+ } catch (error) {
588
+ throw new Error(`Failed to get team projects: ${error.message}`);
589
+ }
590
+ }
591
+
592
+ /**
593
+ * Get project files
594
+ * @param {string} projectId - The project ID
595
+ * @returns {Promise<Object>} The project files data
596
+ */
597
+ async function getProjectFiles(projectId) {
598
+ try {
599
+ const response = await fetch(`${FIGMA_API_BASE}/projects/${projectId}/files`, {
600
+ headers: {
601
+ 'X-Figma-Token': FIGMA_TOKEN,
602
+ },
603
+ });
604
+
605
+ if (!response.ok) {
606
+ throw new Error(`Figma API error: ${response.status} ${response.statusText}`);
607
+ }
608
+
609
+ const data = await response.json();
610
+ return data;
611
+ } catch (error) {
612
+ throw new Error(`Failed to get project files: ${error.message}`);
613
+ }
614
+ }
615
+
616
+ /**
617
+ * Get local variables from a Figma file
618
+ * @param {string} fileId - The Figma file ID
619
+ * @returns {Promise<Object>} The local variables data
620
+ */
621
+ async function getLocalVariables(fileId) {
622
+ try {
623
+ const response = await fetch(`${FIGMA_API_BASE}/files/${fileId}/variables/local`, {
624
+ headers: {
625
+ 'X-Figma-Token': FIGMA_TOKEN,
626
+ },
627
+ });
628
+
629
+ if (!response.ok) {
630
+ throw new Error(`Figma API error: ${response.status} ${response.statusText}`);
631
+ }
632
+
633
+ const data = await response.json();
634
+ return data;
635
+ } catch (error) {
636
+ throw new Error(`Failed to get local variables: ${error.message}`);
637
+ }
638
+ }
639
+
640
+ /**
641
+ * Get published variables from a Figma file
642
+ * @param {string} fileId - The Figma file ID
643
+ * @returns {Promise<Object>} The published variables data
644
+ */
645
+ async function getPublishedVariables(fileId) {
646
+ try {
647
+ const response = await fetch(`${FIGMA_API_BASE}/files/${fileId}/variables/published`, {
648
+ headers: {
649
+ 'X-Figma-Token': FIGMA_TOKEN,
650
+ },
651
+ });
652
+
653
+ if (!response.ok) {
654
+ throw new Error(`Figma API error: ${response.status} ${response.statusText}`);
655
+ }
656
+
657
+ const data = await response.json();
658
+ return data;
659
+ } catch (error) {
660
+ throw new Error(`Failed to get published variables: ${error.message}`);
661
+ }
662
+ }
663
+
664
+ /**
665
+ * Create or update variables in a Figma file
666
+ * @param {string} fileId - The Figma file ID
667
+ * @param {Object} variablesData - The variables data to create/update
668
+ * @returns {Promise<Object>} The created/updated variables
669
+ */
670
+ async function createVariables(fileId, variablesData) {
671
+ try {
672
+ const response = await fetch(`${FIGMA_API_BASE}/files/${fileId}/variables`, {
673
+ method: 'POST',
674
+ headers: {
675
+ 'X-Figma-Token': FIGMA_TOKEN,
676
+ 'Content-Type': 'application/json',
677
+ },
678
+ body: JSON.stringify(variablesData),
679
+ });
680
+
681
+ if (!response.ok) {
682
+ const errorData = await response.json().catch(() => ({}));
683
+ throw new Error(`Figma API error: ${response.status} ${response.statusText} - ${errorData.message || ''}`);
684
+ }
685
+
686
+ const data = await response.json();
687
+ return data;
688
+ } catch (error) {
689
+ throw new Error(`Failed to create variables: ${error.message}`);
690
+ }
691
+ }
692
+
693
+ /**
694
+ * Get dev resources from a Figma file
695
+ * @param {string} fileId - The Figma file ID
696
+ * @param {string} nodeId - Optional node ID to filter resources
697
+ * @returns {Promise<Object>} The dev resources data
698
+ */
699
+ async function getDevResources(fileId, nodeId = null) {
700
+ try {
701
+ const url = nodeId
702
+ ? `${FIGMA_API_BASE}/files/${fileId}/dev_resources?node_id=${encodeURIComponent(nodeId)}`
703
+ : `${FIGMA_API_BASE}/files/${fileId}/dev_resources`;
704
+
705
+ const response = await fetch(url, {
706
+ headers: {
707
+ 'X-Figma-Token': FIGMA_TOKEN,
708
+ },
709
+ });
710
+
711
+ if (!response.ok) {
712
+ throw new Error(`Figma API error: ${response.status} ${response.statusText}`);
713
+ }
714
+
715
+ const data = await response.json();
716
+ return data;
717
+ } catch (error) {
718
+ throw new Error(`Failed to get dev resources: ${error.message}`);
719
+ }
720
+ }
721
+
722
+ /**
723
+ * Create dev resources in a Figma file
724
+ * @param {string} fileId - The Figma file ID
725
+ * @param {Object} devResourceData - The dev resource data (node_id, url, name)
726
+ * @returns {Promise<Object>} The created dev resource
727
+ */
728
+ async function createDevResource(fileId, devResourceData) {
729
+ try {
730
+ const response = await fetch(`${FIGMA_API_BASE}/files/${fileId}/dev_resources`, {
731
+ method: 'POST',
732
+ headers: {
733
+ 'X-Figma-Token': FIGMA_TOKEN,
734
+ 'Content-Type': 'application/json',
735
+ },
736
+ body: JSON.stringify(devResourceData),
737
+ });
738
+
739
+ if (!response.ok) {
740
+ const errorData = await response.json().catch(() => ({}));
741
+ throw new Error(`Figma API error: ${response.status} ${response.statusText} - ${errorData.message || ''}`);
742
+ }
743
+
744
+ const data = await response.json();
745
+ return data;
746
+ } catch (error) {
747
+ throw new Error(`Failed to create dev resource: ${error.message}`);
748
+ }
749
+ }
750
+
751
+ /**
752
+ * Delete a dev resource from a Figma file
753
+ * @param {string} fileId - The Figma file ID
754
+ * @param {string} devResourceId - The dev resource ID to delete
755
+ * @returns {Promise<Object>} The deletion response
756
+ */
757
+ async function deleteDevResource(fileId, devResourceId) {
758
+ try {
759
+ const response = await fetch(`${FIGMA_API_BASE}/files/${fileId}/dev_resources/${devResourceId}`, {
760
+ method: 'DELETE',
761
+ headers: {
762
+ 'X-Figma-Token': FIGMA_TOKEN,
763
+ },
764
+ });
765
+
766
+ if (!response.ok) {
767
+ throw new Error(`Figma API error: ${response.status} ${response.statusText}`);
768
+ }
769
+
770
+ return { success: true, message: 'Dev resource deleted successfully' };
771
+ } catch (error) {
772
+ throw new Error(`Failed to delete dev resource: ${error.message}`);
773
+ }
774
+ }
775
+
776
+ /**
777
+ * Create a webhook
778
+ * @param {Object} webhookData - The webhook data (event_type, team_id, passcode, endpoint, description)
779
+ * @returns {Promise<Object>} The created webhook
780
+ */
781
+ async function createWebhook(webhookData) {
782
+ try {
783
+ const response = await fetch(`${FIGMA_API_BASE.replace('/v1', '/v2')}/webhooks`, {
784
+ method: 'POST',
785
+ headers: {
786
+ 'X-Figma-Token': FIGMA_TOKEN,
787
+ 'Content-Type': 'application/json',
788
+ },
789
+ body: JSON.stringify(webhookData),
790
+ });
791
+
792
+ if (!response.ok) {
793
+ const errorData = await response.json().catch(() => ({}));
794
+ throw new Error(`Figma API error: ${response.status} ${response.statusText} - ${errorData.message || ''}`);
795
+ }
796
+
797
+ const data = await response.json();
798
+ return data;
799
+ } catch (error) {
800
+ throw new Error(`Failed to create webhook: ${error.message}`);
801
+ }
802
+ }
803
+
804
+ /**
805
+ * Get a webhook by ID
806
+ * @param {string} webhookId - The webhook ID
807
+ * @returns {Promise<Object>} The webhook data
808
+ */
809
+ async function getWebhook(webhookId) {
810
+ try {
811
+ const response = await fetch(`${FIGMA_API_BASE.replace('/v1', '/v2')}/webhooks/${webhookId}`, {
812
+ headers: {
813
+ 'X-Figma-Token': FIGMA_TOKEN,
814
+ },
815
+ });
816
+
817
+ if (!response.ok) {
818
+ throw new Error(`Figma API error: ${response.status} ${response.statusText}`);
819
+ }
820
+
821
+ const data = await response.json();
822
+ return data;
823
+ } catch (error) {
824
+ throw new Error(`Failed to get webhook: ${error.message}`);
825
+ }
826
+ }
827
+
828
+ /**
829
+ * Get all webhooks
830
+ * @param {string} teamId - Optional team ID to filter webhooks
831
+ * @returns {Promise<Object>} The webhooks data
832
+ */
833
+ async function getWebhooks(teamId = null) {
834
+ try {
835
+ const url = teamId
836
+ ? `${FIGMA_API_BASE.replace('/v1', '/v2')}/webhooks?team_id=${teamId}`
837
+ : `${FIGMA_API_BASE.replace('/v1', '/v2')}/webhooks`;
838
+
839
+ const response = await fetch(url, {
840
+ headers: {
841
+ 'X-Figma-Token': FIGMA_TOKEN,
842
+ },
843
+ });
844
+
845
+ if (!response.ok) {
846
+ throw new Error(`Figma API error: ${response.status} ${response.statusText}`);
847
+ }
848
+
849
+ const data = await response.json();
850
+ return data;
851
+ } catch (error) {
852
+ throw new Error(`Failed to get webhooks: ${error.message}`);
853
+ }
854
+ }
855
+
856
+ /**
857
+ * Update a webhook
858
+ * @param {string} webhookId - The webhook ID
859
+ * @param {Object} updateData - The data to update (status, description, endpoint, passcode)
860
+ * @returns {Promise<Object>} The updated webhook
861
+ */
862
+ async function updateWebhook(webhookId, updateData) {
863
+ try {
864
+ const response = await fetch(`${FIGMA_API_BASE.replace('/v1', '/v2')}/webhooks/${webhookId}`, {
865
+ method: 'PUT',
866
+ headers: {
867
+ 'X-Figma-Token': FIGMA_TOKEN,
868
+ 'Content-Type': 'application/json',
869
+ },
870
+ body: JSON.stringify(updateData),
871
+ });
872
+
873
+ if (!response.ok) {
874
+ const errorData = await response.json().catch(() => ({}));
875
+ throw new Error(`Figma API error: ${response.status} ${response.statusText} - ${errorData.message || ''}`);
876
+ }
877
+
878
+ const data = await response.json();
879
+ return data;
880
+ } catch (error) {
881
+ throw new Error(`Failed to update webhook: ${error.message}`);
882
+ }
883
+ }
884
+
885
+ /**
886
+ * Delete a webhook
887
+ * @param {string} webhookId - The webhook ID
888
+ * @returns {Promise<Object>} The deletion response
889
+ */
890
+ async function deleteWebhook(webhookId) {
891
+ try {
892
+ const response = await fetch(`${FIGMA_API_BASE.replace('/v1', '/v2')}/webhooks/${webhookId}`, {
893
+ method: 'DELETE',
894
+ headers: {
895
+ 'X-Figma-Token': FIGMA_TOKEN,
896
+ },
897
+ });
898
+
899
+ if (!response.ok) {
900
+ throw new Error(`Figma API error: ${response.status} ${response.statusText}`);
901
+ }
902
+
903
+ return { success: true, message: 'Webhook deleted successfully' };
904
+ } catch (error) {
905
+ throw new Error(`Failed to delete webhook: ${error.message}`);
906
+ }
907
+ }
908
+
909
+ /**
910
+ * Get webhook requests (for debugging)
911
+ * @param {string} webhookId - The webhook ID
912
+ * @returns {Promise<Object>} The webhook requests data
913
+ */
914
+ async function getWebhookRequests(webhookId) {
915
+ try {
916
+ const response = await fetch(`${FIGMA_API_BASE.replace('/v1', '/v2')}/webhooks/${webhookId}/requests`, {
917
+ headers: {
918
+ 'X-Figma-Token': FIGMA_TOKEN,
919
+ },
920
+ });
921
+
922
+ if (!response.ok) {
923
+ throw new Error(`Figma API error: ${response.status} ${response.statusText}`);
924
+ }
925
+
926
+ const data = await response.json();
927
+ return data;
928
+ } catch (error) {
929
+ throw new Error(`Failed to get webhook requests: ${error.message}`);
930
+ }
931
+ }
932
+
933
+ /**
934
+ * Generate a summary of comments
935
+ * @param {Array} comments - Array of comment objects
936
+ * @returns {Object} Summary statistics and structured data
937
+ */
938
+ function summarizeComments(comments) {
939
+ const totalComments = comments.length;
940
+ const totalReplies = comments.reduce((sum, c) => sum + c.replies.length, 0);
941
+ const resolvedComments = comments.filter(c => c.resolvedAt).length;
942
+ const unresolvedComments = totalComments - resolvedComments;
943
+
944
+ // Group by author
945
+ const authorStats = {};
946
+ comments.forEach(comment => {
947
+ const handle = comment.author.handle;
948
+ if (!authorStats[handle]) {
949
+ authorStats[handle] = { comments: 0, replies: 0 };
950
+ }
951
+ authorStats[handle].comments++;
952
+
953
+ comment.replies.forEach(reply => {
954
+ const replyHandle = reply.author.handle;
955
+ if (!authorStats[replyHandle]) {
956
+ authorStats[replyHandle] = { comments: 0, replies: 0 };
957
+ }
958
+ authorStats[replyHandle].replies++;
959
+ });
960
+ });
961
+
962
+ // Group by location (node)
963
+ const locationStats = {};
964
+ comments.forEach(comment => {
965
+ if (comment.location && comment.location.node_id) {
966
+ const nodeId = comment.location.node_id;
967
+ locationStats[nodeId] = (locationStats[nodeId] || 0) + 1;
968
+ }
969
+ });
970
+
971
+ return {
972
+ summary: {
973
+ total_comments: totalComments,
974
+ total_replies: totalReplies,
975
+ resolved: resolvedComments,
976
+ unresolved: unresolvedComments,
977
+ },
978
+ by_author: authorStats,
979
+ by_location: locationStats,
980
+ comments: comments.map(c => ({
981
+ id: c.id,
982
+ preview: c.message.substring(0, 100) + (c.message.length > 100 ? '...' : ''),
983
+ author: c.author.handle,
984
+ timestamp: c.timestamp,
985
+ replies_count: c.replies.length,
986
+ resolved: !!c.resolvedAt,
987
+ location: c.location,
988
+ })),
989
+ };
990
+ }
991
+
992
+ // Create MCP server
993
+ const server = new Server(
994
+ {
995
+ name: 'mcp-figma-comment-summary',
996
+ version: '2.0.0',
997
+ },
998
+ {
999
+ capabilities: {
1000
+ tools: {},
1001
+ },
1002
+ }
1003
+ );
1004
+
1005
+ // Define available tools
1006
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
1007
+ return {
1008
+ tools: [
1009
+ // Comments endpoints
1010
+ {
1011
+ name: 'get_figma_comments',
1012
+ description: 'Retrieve all comments from a Figma file, including comment text, author info, timestamps, location, and reply threads',
1013
+ inputSchema: {
1014
+ type: 'object',
1015
+ properties: {
1016
+ file_id: {
1017
+ type: 'string',
1018
+ description: 'The Figma file ID (from the URL: figma.com/file/FILE_ID/...)',
1019
+ },
1020
+ },
1021
+ required: ['file_id'],
1022
+ },
1023
+ },
1024
+ {
1025
+ name: 'summarize_figma_comments',
1026
+ description: 'Get a summary and analysis of all comments in a Figma file, including statistics by author, location, and resolution status',
1027
+ inputSchema: {
1028
+ type: 'object',
1029
+ properties: {
1030
+ file_id: {
1031
+ type: 'string',
1032
+ description: 'The Figma file ID (from the URL: figma.com/file/FILE_ID/...)',
1033
+ },
1034
+ },
1035
+ required: ['file_id'],
1036
+ },
1037
+ },
1038
+ {
1039
+ name: 'post_figma_comment',
1040
+ description: 'Create a new comment on a Figma file. Optionally specify a location (node_id and/or coordinates) where the comment should be placed',
1041
+ inputSchema: {
1042
+ type: 'object',
1043
+ properties: {
1044
+ file_id: {
1045
+ type: 'string',
1046
+ description: 'The Figma file ID (from the URL: figma.com/file/FILE_ID/...)',
1047
+ },
1048
+ message: {
1049
+ type: 'string',
1050
+ description: 'The text content of the comment. You can mention users using @username format (e.g., "Hey @john, please review this")',
1051
+ },
1052
+ node_id: {
1053
+ type: 'string',
1054
+ description: 'Optional: The ID of the node/element to attach the comment to',
1055
+ },
1056
+ x: {
1057
+ type: 'number',
1058
+ description: 'Optional: X coordinate for the comment position',
1059
+ },
1060
+ y: {
1061
+ type: 'number',
1062
+ description: 'Optional: Y coordinate for the comment position',
1063
+ },
1064
+ },
1065
+ required: ['file_id', 'message'],
1066
+ },
1067
+ },
1068
+ {
1069
+ name: 'reply_to_figma_comment',
1070
+ description: 'Reply to an existing comment in a Figma file',
1071
+ inputSchema: {
1072
+ type: 'object',
1073
+ properties: {
1074
+ file_id: {
1075
+ type: 'string',
1076
+ description: 'The Figma file ID (from the URL: figma.com/file/FILE_ID/...)',
1077
+ },
1078
+ comment_id: {
1079
+ type: 'string',
1080
+ description: 'The ID of the comment to reply to',
1081
+ },
1082
+ message: {
1083
+ type: 'string',
1084
+ description: 'The text content of the reply. You can mention users using @username format (e.g., "@maria I agree with your point")',
1085
+ },
1086
+ },
1087
+ required: ['file_id', 'comment_id', 'message'],
1088
+ },
1089
+ },
1090
+ {
1091
+ name: 'delete_figma_comment',
1092
+ description: 'Delete a comment from a Figma file',
1093
+ inputSchema: {
1094
+ type: 'object',
1095
+ properties: {
1096
+ file_id: {
1097
+ type: 'string',
1098
+ description: 'The Figma file ID (from the URL: figma.com/file/FILE_ID/...)',
1099
+ },
1100
+ comment_id: {
1101
+ type: 'string',
1102
+ description: 'The ID of the comment to delete',
1103
+ },
1104
+ },
1105
+ required: ['file_id', 'comment_id'],
1106
+ },
1107
+ },
1108
+ {
1109
+ name: 'get_comment_reactions',
1110
+ description: 'Get all reactions for a specific comment',
1111
+ inputSchema: {
1112
+ type: 'object',
1113
+ properties: {
1114
+ file_id: {
1115
+ type: 'string',
1116
+ description: 'The Figma file ID (from the URL: figma.com/file/FILE_ID/...)',
1117
+ },
1118
+ comment_id: {
1119
+ type: 'string',
1120
+ description: 'The ID of the comment',
1121
+ },
1122
+ },
1123
+ required: ['file_id', 'comment_id'],
1124
+ },
1125
+ },
1126
+ {
1127
+ name: 'post_comment_reaction',
1128
+ description: 'Add a reaction (emoji) to a comment',
1129
+ inputSchema: {
1130
+ type: 'object',
1131
+ properties: {
1132
+ file_id: {
1133
+ type: 'string',
1134
+ description: 'The Figma file ID (from the URL: figma.com/file/FILE_ID/...)',
1135
+ },
1136
+ comment_id: {
1137
+ type: 'string',
1138
+ description: 'The ID of the comment',
1139
+ },
1140
+ emoji: {
1141
+ type: 'string',
1142
+ description: 'The emoji reaction (e.g., "👍", "❤️", "😊")',
1143
+ },
1144
+ },
1145
+ required: ['file_id', 'comment_id', 'emoji'],
1146
+ },
1147
+ },
1148
+ {
1149
+ name: 'delete_comment_reaction',
1150
+ description: 'Remove a reaction (emoji) from a comment',
1151
+ inputSchema: {
1152
+ type: 'object',
1153
+ properties: {
1154
+ file_id: {
1155
+ type: 'string',
1156
+ description: 'The Figma file ID (from the URL: figma.com/file/FILE_ID/...)',
1157
+ },
1158
+ comment_id: {
1159
+ type: 'string',
1160
+ description: 'The ID of the comment',
1161
+ },
1162
+ emoji: {
1163
+ type: 'string',
1164
+ description: 'The emoji reaction to remove (e.g., "👍", "❤️", "😊")',
1165
+ },
1166
+ },
1167
+ required: ['file_id', 'comment_id', 'emoji'],
1168
+ },
1169
+ },
1170
+ {
1171
+ name: 'get_comment_users',
1172
+ description: 'Get a list of all users who have commented on a Figma file. Useful to see who can be mentioned in comments using @username',
1173
+ inputSchema: {
1174
+ type: 'object',
1175
+ properties: {
1176
+ file_id: {
1177
+ type: 'string',
1178
+ description: 'The Figma file ID (from the URL: figma.com/file/FILE_ID/...)',
1179
+ },
1180
+ },
1181
+ required: ['file_id'],
1182
+ },
1183
+ },
1184
+ // Files endpoints
1185
+ {
1186
+ name: 'get_file',
1187
+ description: 'Get a Figma file as JSON with complete document structure, components, and metadata. Supports filtering by version, specific nodes, depth, and geometry',
1188
+ inputSchema: {
1189
+ type: 'object',
1190
+ properties: {
1191
+ file_id: {
1192
+ type: 'string',
1193
+ description: 'The Figma file ID (from the URL: figma.com/file/FILE_ID/...)',
1194
+ },
1195
+ version: {
1196
+ type: 'string',
1197
+ description: 'Optional: A specific version ID to retrieve',
1198
+ },
1199
+ ids: {
1200
+ type: 'string',
1201
+ description: 'Optional: Comma-separated list of node IDs to retrieve (e.g., "1:5,1:6")',
1202
+ },
1203
+ depth: {
1204
+ type: 'number',
1205
+ description: 'Optional: Depth to traverse (default: traverse all)',
1206
+ },
1207
+ geometry: {
1208
+ type: 'string',
1209
+ description: 'Optional: Set to "paths" to export vector data',
1210
+ },
1211
+ },
1212
+ required: ['file_id'],
1213
+ },
1214
+ },
1215
+ {
1216
+ name: 'get_file_nodes',
1217
+ description: 'Get specific nodes from a Figma file by their IDs',
1218
+ inputSchema: {
1219
+ type: 'object',
1220
+ properties: {
1221
+ file_id: {
1222
+ type: 'string',
1223
+ description: 'The Figma file ID (from the URL: figma.com/file/FILE_ID/...)',
1224
+ },
1225
+ ids: {
1226
+ type: 'string',
1227
+ description: 'Comma-separated list of node IDs to retrieve (e.g., "1:5,1:6,1:7")',
1228
+ },
1229
+ },
1230
+ required: ['file_id', 'ids'],
1231
+ },
1232
+ },
1233
+ {
1234
+ name: 'render_images',
1235
+ description: 'Render images from file nodes in various formats (JPG, PNG, SVG, PDF) with customizable scale and options',
1236
+ inputSchema: {
1237
+ type: 'object',
1238
+ properties: {
1239
+ file_id: {
1240
+ type: 'string',
1241
+ description: 'The Figma file ID (from the URL: figma.com/file/FILE_ID/...)',
1242
+ },
1243
+ ids: {
1244
+ type: 'string',
1245
+ description: 'Comma-separated list of node IDs to render as images (e.g., "1:5,1:6")',
1246
+ },
1247
+ scale: {
1248
+ type: 'number',
1249
+ description: 'Optional: Image scale (between 0.01 and 4)',
1250
+ },
1251
+ format: {
1252
+ type: 'string',
1253
+ description: 'Optional: Image format (jpg, png, svg, pdf). Default: png',
1254
+ },
1255
+ svg_include_id: {
1256
+ type: 'boolean',
1257
+ description: 'Optional: Whether to include id attributes in SVG',
1258
+ },
1259
+ svg_simplify_stroke: {
1260
+ type: 'boolean',
1261
+ description: 'Optional: Whether to simplify strokes in SVG',
1262
+ },
1263
+ use_absolute_bounds: {
1264
+ type: 'boolean',
1265
+ description: 'Optional: Use absolute bounding box',
1266
+ },
1267
+ },
1268
+ required: ['file_id', 'ids'],
1269
+ },
1270
+ },
1271
+ {
1272
+ name: 'get_image_fills',
1273
+ description: 'Get download links for all images used in image fills throughout the document',
1274
+ inputSchema: {
1275
+ type: 'object',
1276
+ properties: {
1277
+ file_id: {
1278
+ type: 'string',
1279
+ description: 'The Figma file ID (from the URL: figma.com/file/FILE_ID/...)',
1280
+ },
1281
+ },
1282
+ required: ['file_id'],
1283
+ },
1284
+ },
1285
+ // Users endpoint
1286
+ {
1287
+ name: 'get_current_user',
1288
+ description: 'Get information about the current user (the owner of the access token)',
1289
+ inputSchema: {
1290
+ type: 'object',
1291
+ properties: {},
1292
+ },
1293
+ },
1294
+ // Version History endpoint
1295
+ {
1296
+ name: 'get_version_history',
1297
+ description: 'Get the version history of a Figma file',
1298
+ inputSchema: {
1299
+ type: 'object',
1300
+ properties: {
1301
+ file_id: {
1302
+ type: 'string',
1303
+ description: 'The Figma file ID (from the URL: figma.com/file/FILE_ID/...)',
1304
+ },
1305
+ },
1306
+ required: ['file_id'],
1307
+ },
1308
+ },
1309
+ // Projects & Teams endpoints
1310
+ {
1311
+ name: 'get_team_projects',
1312
+ description: 'Get all projects for a specific team',
1313
+ inputSchema: {
1314
+ type: 'object',
1315
+ properties: {
1316
+ team_id: {
1317
+ type: 'string',
1318
+ description: 'The team ID',
1319
+ },
1320
+ },
1321
+ required: ['team_id'],
1322
+ },
1323
+ },
1324
+ {
1325
+ name: 'get_project_files',
1326
+ description: 'Get all files within a specific project',
1327
+ inputSchema: {
1328
+ type: 'object',
1329
+ properties: {
1330
+ project_id: {
1331
+ type: 'string',
1332
+ description: 'The project ID',
1333
+ },
1334
+ },
1335
+ required: ['project_id'],
1336
+ },
1337
+ },
1338
+ // Variables endpoints
1339
+ {
1340
+ name: 'get_local_variables',
1341
+ description: 'Get local variables defined in a Figma file (requires Enterprise plan)',
1342
+ inputSchema: {
1343
+ type: 'object',
1344
+ properties: {
1345
+ file_id: {
1346
+ type: 'string',
1347
+ description: 'The Figma file ID (from the URL: figma.com/file/FILE_ID/...)',
1348
+ },
1349
+ },
1350
+ required: ['file_id'],
1351
+ },
1352
+ },
1353
+ {
1354
+ name: 'get_published_variables',
1355
+ description: 'Get published variables from a Figma file (requires Enterprise plan)',
1356
+ inputSchema: {
1357
+ type: 'object',
1358
+ properties: {
1359
+ file_id: {
1360
+ type: 'string',
1361
+ description: 'The Figma file ID (from the URL: figma.com/file/FILE_ID/...)',
1362
+ },
1363
+ },
1364
+ required: ['file_id'],
1365
+ },
1366
+ },
1367
+ {
1368
+ name: 'create_variables',
1369
+ description: 'Create or update variables in a Figma file (requires Enterprise plan with file_variables:write scope)',
1370
+ inputSchema: {
1371
+ type: 'object',
1372
+ properties: {
1373
+ file_id: {
1374
+ type: 'string',
1375
+ description: 'The Figma file ID (from the URL: figma.com/file/FILE_ID/...)',
1376
+ },
1377
+ variables_data: {
1378
+ type: 'object',
1379
+ description: 'The variables data to create/update (refer to Figma API docs for structure)',
1380
+ },
1381
+ },
1382
+ required: ['file_id', 'variables_data'],
1383
+ },
1384
+ },
1385
+ // Dev Resources endpoints
1386
+ {
1387
+ name: 'get_dev_resources',
1388
+ description: 'Get dev resources (developer-contributed URLs) attached to nodes in a Figma file',
1389
+ inputSchema: {
1390
+ type: 'object',
1391
+ properties: {
1392
+ file_id: {
1393
+ type: 'string',
1394
+ description: 'The Figma file ID (from the URL: figma.com/file/FILE_ID/...)',
1395
+ },
1396
+ node_id: {
1397
+ type: 'string',
1398
+ description: 'Optional: Filter resources by specific node ID',
1399
+ },
1400
+ },
1401
+ required: ['file_id'],
1402
+ },
1403
+ },
1404
+ {
1405
+ name: 'create_dev_resource',
1406
+ description: 'Create a dev resource (developer-contributed URL) attached to a node in a Figma file',
1407
+ inputSchema: {
1408
+ type: 'object',
1409
+ properties: {
1410
+ file_id: {
1411
+ type: 'string',
1412
+ description: 'The Figma file ID (from the URL: figma.com/file/FILE_ID/...)',
1413
+ },
1414
+ node_id: {
1415
+ type: 'string',
1416
+ description: 'The node ID to attach the resource to',
1417
+ },
1418
+ url: {
1419
+ type: 'string',
1420
+ description: 'The URL of the dev resource',
1421
+ },
1422
+ name: {
1423
+ type: 'string',
1424
+ description: 'The name/title of the dev resource',
1425
+ },
1426
+ },
1427
+ required: ['file_id', 'node_id', 'url', 'name'],
1428
+ },
1429
+ },
1430
+ {
1431
+ name: 'delete_dev_resource',
1432
+ description: 'Delete a dev resource from a Figma file',
1433
+ inputSchema: {
1434
+ type: 'object',
1435
+ properties: {
1436
+ file_id: {
1437
+ type: 'string',
1438
+ description: 'The Figma file ID (from the URL: figma.com/file/FILE_ID/...)',
1439
+ },
1440
+ dev_resource_id: {
1441
+ type: 'string',
1442
+ description: 'The ID of the dev resource to delete',
1443
+ },
1444
+ },
1445
+ required: ['file_id', 'dev_resource_id'],
1446
+ },
1447
+ },
1448
+ // Webhooks V2 endpoints
1449
+ {
1450
+ name: 'create_webhook',
1451
+ description: 'Create a new webhook for Figma events (requires webhooks:write scope)',
1452
+ inputSchema: {
1453
+ type: 'object',
1454
+ properties: {
1455
+ event_type: {
1456
+ type: 'string',
1457
+ description: 'The event type (e.g., "FILE_UPDATE", "FILE_VERSION_UPDATE", "LIBRARY_PUBLISH")',
1458
+ },
1459
+ team_id: {
1460
+ type: 'string',
1461
+ description: 'The team ID',
1462
+ },
1463
+ endpoint: {
1464
+ type: 'string',
1465
+ description: 'The URL endpoint to receive webhook events',
1466
+ },
1467
+ passcode: {
1468
+ type: 'string',
1469
+ description: 'A passcode for webhook verification',
1470
+ },
1471
+ description: {
1472
+ type: 'string',
1473
+ description: 'Optional: Description of the webhook',
1474
+ },
1475
+ },
1476
+ required: ['event_type', 'team_id', 'endpoint', 'passcode'],
1477
+ },
1478
+ },
1479
+ {
1480
+ name: 'get_webhook',
1481
+ description: 'Get details of a specific webhook by ID',
1482
+ inputSchema: {
1483
+ type: 'object',
1484
+ properties: {
1485
+ webhook_id: {
1486
+ type: 'string',
1487
+ description: 'The webhook ID',
1488
+ },
1489
+ },
1490
+ required: ['webhook_id'],
1491
+ },
1492
+ },
1493
+ {
1494
+ name: 'get_webhooks',
1495
+ description: 'Get all webhooks, optionally filtered by team',
1496
+ inputSchema: {
1497
+ type: 'object',
1498
+ properties: {
1499
+ team_id: {
1500
+ type: 'string',
1501
+ description: 'Optional: Filter webhooks by team ID',
1502
+ },
1503
+ },
1504
+ },
1505
+ },
1506
+ {
1507
+ name: 'update_webhook',
1508
+ description: 'Update an existing webhook',
1509
+ inputSchema: {
1510
+ type: 'object',
1511
+ properties: {
1512
+ webhook_id: {
1513
+ type: 'string',
1514
+ description: 'The webhook ID to update',
1515
+ },
1516
+ status: {
1517
+ type: 'string',
1518
+ description: 'Optional: Webhook status (ACTIVE or PAUSED)',
1519
+ },
1520
+ description: {
1521
+ type: 'string',
1522
+ description: 'Optional: New description',
1523
+ },
1524
+ endpoint: {
1525
+ type: 'string',
1526
+ description: 'Optional: New endpoint URL',
1527
+ },
1528
+ passcode: {
1529
+ type: 'string',
1530
+ description: 'Optional: New passcode',
1531
+ },
1532
+ },
1533
+ required: ['webhook_id'],
1534
+ },
1535
+ },
1536
+ {
1537
+ name: 'delete_webhook',
1538
+ description: 'Delete a webhook',
1539
+ inputSchema: {
1540
+ type: 'object',
1541
+ properties: {
1542
+ webhook_id: {
1543
+ type: 'string',
1544
+ description: 'The webhook ID to delete',
1545
+ },
1546
+ },
1547
+ required: ['webhook_id'],
1548
+ },
1549
+ },
1550
+ {
1551
+ name: 'get_webhook_requests',
1552
+ description: 'Get webhook requests for debugging (shows requests from the past 7 days)',
1553
+ inputSchema: {
1554
+ type: 'object',
1555
+ properties: {
1556
+ webhook_id: {
1557
+ type: 'string',
1558
+ description: 'The webhook ID',
1559
+ },
1560
+ },
1561
+ required: ['webhook_id'],
1562
+ },
1563
+ },
1564
+ ],
1565
+ };
1566
+ });
1567
+
1568
+ // Handle tool calls
1569
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1570
+ const { name, arguments: args } = request.params;
1571
+
1572
+ try {
1573
+ if (name === 'get_figma_comments') {
1574
+ const { file_id } = args;
1575
+
1576
+ if (!file_id) {
1577
+ throw new Error('file_id is required');
1578
+ }
1579
+
1580
+ const comments = await getFigmaComments(file_id);
1581
+
1582
+ return {
1583
+ content: [
1584
+ {
1585
+ type: 'text',
1586
+ text: JSON.stringify(comments, null, 2),
1587
+ },
1588
+ ],
1589
+ };
1590
+ }
1591
+
1592
+ if (name === 'summarize_figma_comments') {
1593
+ const { file_id } = args;
1594
+
1595
+ if (!file_id) {
1596
+ throw new Error('file_id is required');
1597
+ }
1598
+
1599
+ const comments = await getFigmaComments(file_id);
1600
+ const summary = summarizeComments(comments);
1601
+
1602
+ return {
1603
+ content: [
1604
+ {
1605
+ type: 'text',
1606
+ text: JSON.stringify(summary, null, 2),
1607
+ },
1608
+ ],
1609
+ };
1610
+ }
1611
+
1612
+ if (name === 'post_figma_comment') {
1613
+ const { file_id, message, node_id, x, y } = args;
1614
+
1615
+ if (!file_id) {
1616
+ throw new Error('file_id is required');
1617
+ }
1618
+
1619
+ if (!message) {
1620
+ throw new Error('message is required');
1621
+ }
1622
+
1623
+ // Build location object if any location parameters provided
1624
+ const location = {};
1625
+ if (node_id) location.node_id = node_id;
1626
+ if (x !== undefined) location.x = x;
1627
+ if (y !== undefined) location.y = y;
1628
+
1629
+ const result = await postFigmaComment(
1630
+ file_id,
1631
+ message,
1632
+ Object.keys(location).length > 0 ? location : null
1633
+ );
1634
+
1635
+ return {
1636
+ content: [
1637
+ {
1638
+ type: 'text',
1639
+ text: `Comment posted successfully!\n\n${JSON.stringify(result, null, 2)}`,
1640
+ },
1641
+ ],
1642
+ };
1643
+ }
1644
+
1645
+ if (name === 'reply_to_figma_comment') {
1646
+ const { file_id, comment_id, message } = args;
1647
+
1648
+ if (!file_id) {
1649
+ throw new Error('file_id is required');
1650
+ }
1651
+
1652
+ if (!comment_id) {
1653
+ throw new Error('comment_id is required');
1654
+ }
1655
+
1656
+ if (!message) {
1657
+ throw new Error('message is required');
1658
+ }
1659
+
1660
+ const result = await replyToFigmaComment(file_id, comment_id, message);
1661
+
1662
+ return {
1663
+ content: [
1664
+ {
1665
+ type: 'text',
1666
+ text: `Reply posted successfully!\n\n${JSON.stringify(result, null, 2)}`,
1667
+ },
1668
+ ],
1669
+ };
1670
+ }
1671
+
1672
+ if (name === 'get_comment_users') {
1673
+ const { file_id } = args;
1674
+
1675
+ if (!file_id) {
1676
+ throw new Error('file_id is required');
1677
+ }
1678
+
1679
+ const { users } = await getCommentUsers(file_id);
1680
+
1681
+ return {
1682
+ content: [
1683
+ {
1684
+ type: 'text',
1685
+ text: `Found ${users.length} user(s) who have commented:\n\n${JSON.stringify(users, null, 2)}\n\nYou can mention these users in comments using @${users.map(u => u.handle).join(', @')}`,
1686
+ },
1687
+ ],
1688
+ };
1689
+ }
1690
+
1691
+ // Comment deletion and reactions
1692
+ if (name === 'delete_figma_comment') {
1693
+ const { file_id, comment_id } = args;
1694
+
1695
+ if (!file_id) {
1696
+ throw new Error('file_id is required');
1697
+ }
1698
+
1699
+ if (!comment_id) {
1700
+ throw new Error('comment_id is required');
1701
+ }
1702
+
1703
+ const result = await deleteFigmaComment(file_id, comment_id);
1704
+
1705
+ return {
1706
+ content: [
1707
+ {
1708
+ type: 'text',
1709
+ text: `${result.message}\n\n${JSON.stringify(result, null, 2)}`,
1710
+ },
1711
+ ],
1712
+ };
1713
+ }
1714
+
1715
+ if (name === 'get_comment_reactions') {
1716
+ const { file_id, comment_id } = args;
1717
+
1718
+ if (!file_id) {
1719
+ throw new Error('file_id is required');
1720
+ }
1721
+
1722
+ if (!comment_id) {
1723
+ throw new Error('comment_id is required');
1724
+ }
1725
+
1726
+ const result = await getCommentReactions(file_id, comment_id);
1727
+
1728
+ return {
1729
+ content: [
1730
+ {
1731
+ type: 'text',
1732
+ text: JSON.stringify(result, null, 2),
1733
+ },
1734
+ ],
1735
+ };
1736
+ }
1737
+
1738
+ if (name === 'post_comment_reaction') {
1739
+ const { file_id, comment_id, emoji } = args;
1740
+
1741
+ if (!file_id) {
1742
+ throw new Error('file_id is required');
1743
+ }
1744
+
1745
+ if (!comment_id) {
1746
+ throw new Error('comment_id is required');
1747
+ }
1748
+
1749
+ if (!emoji) {
1750
+ throw new Error('emoji is required');
1751
+ }
1752
+
1753
+ const result = await postCommentReaction(file_id, comment_id, emoji);
1754
+
1755
+ return {
1756
+ content: [
1757
+ {
1758
+ type: 'text',
1759
+ text: `Reaction posted successfully!\n\n${JSON.stringify(result, null, 2)}`,
1760
+ },
1761
+ ],
1762
+ };
1763
+ }
1764
+
1765
+ if (name === 'delete_comment_reaction') {
1766
+ const { file_id, comment_id, emoji } = args;
1767
+
1768
+ if (!file_id) {
1769
+ throw new Error('file_id is required');
1770
+ }
1771
+
1772
+ if (!comment_id) {
1773
+ throw new Error('comment_id is required');
1774
+ }
1775
+
1776
+ if (!emoji) {
1777
+ throw new Error('emoji is required');
1778
+ }
1779
+
1780
+ const result = await deleteCommentReaction(file_id, comment_id, emoji);
1781
+
1782
+ return {
1783
+ content: [
1784
+ {
1785
+ type: 'text',
1786
+ text: `${result.message}\n\n${JSON.stringify(result, null, 2)}`,
1787
+ },
1788
+ ],
1789
+ };
1790
+ }
1791
+
1792
+ // Files endpoints
1793
+ if (name === 'get_file') {
1794
+ const { file_id, version, ids, depth, geometry } = args;
1795
+
1796
+ if (!file_id) {
1797
+ throw new Error('file_id is required');
1798
+ }
1799
+
1800
+ const options = {};
1801
+ if (version) options.version = version;
1802
+ if (ids) options.ids = ids;
1803
+ if (depth) options.depth = depth;
1804
+ if (geometry) options.geometry = geometry;
1805
+
1806
+ const result = await getFile(file_id, options);
1807
+
1808
+ return {
1809
+ content: [
1810
+ {
1811
+ type: 'text',
1812
+ text: JSON.stringify(result, null, 2),
1813
+ },
1814
+ ],
1815
+ };
1816
+ }
1817
+
1818
+ if (name === 'get_file_nodes') {
1819
+ const { file_id, ids } = args;
1820
+
1821
+ if (!file_id) {
1822
+ throw new Error('file_id is required');
1823
+ }
1824
+
1825
+ if (!ids) {
1826
+ throw new Error('ids is required');
1827
+ }
1828
+
1829
+ const result = await getFileNodes(file_id, ids);
1830
+
1831
+ return {
1832
+ content: [
1833
+ {
1834
+ type: 'text',
1835
+ text: JSON.stringify(result, null, 2),
1836
+ },
1837
+ ],
1838
+ };
1839
+ }
1840
+
1841
+ if (name === 'render_images') {
1842
+ const { file_id, ids, scale, format, svg_include_id, svg_simplify_stroke, use_absolute_bounds } = args;
1843
+
1844
+ if (!file_id) {
1845
+ throw new Error('file_id is required');
1846
+ }
1847
+
1848
+ if (!ids) {
1849
+ throw new Error('ids is required');
1850
+ }
1851
+
1852
+ const options = { ids };
1853
+ if (scale) options.scale = scale;
1854
+ if (format) options.format = format;
1855
+ if (svg_include_id !== undefined) options.svg_include_id = svg_include_id;
1856
+ if (svg_simplify_stroke !== undefined) options.svg_simplify_stroke = svg_simplify_stroke;
1857
+ if (use_absolute_bounds !== undefined) options.use_absolute_bounds = use_absolute_bounds;
1858
+
1859
+ const result = await renderImages(file_id, options);
1860
+
1861
+ return {
1862
+ content: [
1863
+ {
1864
+ type: 'text',
1865
+ text: JSON.stringify(result, null, 2),
1866
  },
1867
+ ],
1868
+ };
1869
+ }
1870
+
1871
+ if (name === 'get_image_fills') {
1872
+ const { file_id } = args;
1873
+
1874
+ if (!file_id) {
1875
+ throw new Error('file_id is required');
1876
+ }
1877
+
1878
+ const result = await getImageFills(file_id);
1879
+
1880
+ return {
1881
+ content: [
1882
+ {
1883
+ type: 'text',
1884
+ text: JSON.stringify(result, null, 2),
 
 
 
1885
  },
1886
+ ],
1887
+ };
1888
+ }
1889
+
1890
+ // Users endpoint
1891
+ if (name === 'get_current_user') {
1892
+ const result = await getCurrentUser();
1893
+
1894
+ return {
1895
+ content: [
1896
+ {
1897
+ type: 'text',
1898
+ text: JSON.stringify(result, null, 2),
1899
  },
1900
+ ],
1901
+ };
1902
+ }
 
 
 
1903
 
1904
+ // Version History endpoint
1905
+ if (name === 'get_version_history') {
1906
+ const { file_id } = args;
1907
 
1908
+ if (!file_id) {
1909
+ throw new Error('file_id is required');
1910
+ }
1911
+
1912
+ const result = await getVersionHistory(file_id);
1913
+
1914
+ return {
1915
+ content: [
1916
+ {
1917
+ type: 'text',
1918
+ text: JSON.stringify(result, null, 2),
1919
+ },
1920
+ ],
1921
+ };
1922
+ }
1923
+
1924
+ // Projects & Teams endpoints
1925
+ if (name === 'get_team_projects') {
1926
+ const { team_id } = args;
1927
+
1928
+ if (!team_id) {
1929
+ throw new Error('team_id is required');
1930
+ }
1931
+
1932
+ const result = await getTeamProjects(team_id);
1933
+
1934
+ return {
1935
+ content: [
1936
+ {
1937
+ type: 'text',
1938
+ text: JSON.stringify(result, null, 2),
1939
+ },
1940
+ ],
1941
+ };
1942
+ }
1943
+
1944
+ if (name === 'get_project_files') {
1945
+ const { project_id } = args;
1946
+
1947
+ if (!project_id) {
1948
+ throw new Error('project_id is required');
1949
+ }
1950
+
1951
+ const result = await getProjectFiles(project_id);
1952
+
1953
+ return {
1954
+ content: [
1955
+ {
1956
+ type: 'text',
1957
+ text: JSON.stringify(result, null, 2),
1958
+ },
1959
+ ],
1960
+ };
1961
+ }
1962
+
1963
+ // Variables endpoints
1964
+ if (name === 'get_local_variables') {
1965
  const { file_id } = args;
1966
 
1967
  if (!file_id) {
1968
  throw new Error('file_id is required');
1969
  }
1970
 
1971
+ const result = await getLocalVariables(file_id);
1972
 
1973
  return {
1974
  content: [
1975
  {
1976
  type: 'text',
1977
+ text: JSON.stringify(result, null, 2),
1978
  },
1979
  ],
1980
  };
1981
  }
1982
 
1983
+ if (name === 'get_published_variables') {
1984
  const { file_id } = args;
1985
 
1986
  if (!file_id) {
1987
  throw new Error('file_id is required');
1988
  }
1989
 
1990
+ const result = await getPublishedVariables(file_id);
 
1991
 
1992
  return {
1993
  content: [
1994
  {
1995
  type: 'text',
1996
+ text: JSON.stringify(result, null, 2),
1997
  },
1998
  ],
1999
  };
2000
  }
2001
 
2002
+ if (name === 'create_variables') {
2003
+ const { file_id, variables_data } = args;
2004
 
2005
  if (!file_id) {
2006
  throw new Error('file_id is required');
2007
  }
2008
 
2009
+ if (!variables_data) {
2010
+ throw new Error('variables_data is required');
2011
  }
2012
 
2013
+ const result = await createVariables(file_id, variables_data);
 
 
 
 
2014
 
2015
+ return {
2016
+ content: [
2017
+ {
2018
+ type: 'text',
2019
+ text: `Variables created/updated successfully!\n\n${JSON.stringify(result, null, 2)}`,
2020
+ },
2021
+ ],
2022
+ };
2023
+ }
2024
+
2025
+ // Dev Resources endpoints
2026
+ if (name === 'get_dev_resources') {
2027
+ const { file_id, node_id } = args;
2028
+
2029
+ if (!file_id) {
2030
+ throw new Error('file_id is required');
2031
+ }
2032
+
2033
+ const result = await getDevResources(file_id, node_id || null);
2034
 
2035
  return {
2036
  content: [
2037
  {
2038
  type: 'text',
2039
+ text: JSON.stringify(result, null, 2),
2040
  },
2041
  ],
2042
  };
2043
  }
2044
 
2045
+ if (name === 'create_dev_resource') {
2046
+ const { file_id, node_id, url, name: resourceName } = args;
2047
 
2048
  if (!file_id) {
2049
  throw new Error('file_id is required');
2050
  }
2051
 
2052
+ if (!node_id) {
2053
+ throw new Error('node_id is required');
2054
  }
2055
 
2056
+ if (!url) {
2057
+ throw new Error('url is required');
2058
  }
2059
 
2060
+ if (!resourceName) {
2061
+ throw new Error('name is required');
2062
+ }
2063
+
2064
+ const devResourceData = {
2065
+ node_id,
2066
+ url,
2067
+ name: resourceName,
2068
+ };
2069
+
2070
+ const result = await createDevResource(file_id, devResourceData);
2071
 
2072
  return {
2073
  content: [
2074
  {
2075
  type: 'text',
2076
+ text: `Dev resource created successfully!\n\n${JSON.stringify(result, null, 2)}`,
2077
  },
2078
  ],
2079
  };
2080
  }
2081
 
2082
+ if (name === 'delete_dev_resource') {
2083
+ const { file_id, dev_resource_id } = args;
2084
 
2085
  if (!file_id) {
2086
  throw new Error('file_id is required');
2087
  }
2088
 
2089
+ if (!dev_resource_id) {
2090
+ throw new Error('dev_resource_id is required');
2091
+ }
2092
+
2093
+ const result = await deleteDevResource(file_id, dev_resource_id);
2094
 
2095
  return {
2096
  content: [
2097
  {
2098
  type: 'text',
2099
+ text: `${result.message}\n\n${JSON.stringify(result, null, 2)}`,
2100
+ },
2101
+ ],
2102
+ };
2103
+ }
2104
+
2105
+ // Webhooks V2 endpoints
2106
+ if (name === 'create_webhook') {
2107
+ const { event_type, team_id, endpoint, passcode, description } = args;
2108
+
2109
+ if (!event_type) {
2110
+ throw new Error('event_type is required');
2111
+ }
2112
+
2113
+ if (!team_id) {
2114
+ throw new Error('team_id is required');
2115
+ }
2116
+
2117
+ if (!endpoint) {
2118
+ throw new Error('endpoint is required');
2119
+ }
2120
+
2121
+ if (!passcode) {
2122
+ throw new Error('passcode is required');
2123
+ }
2124
+
2125
+ const webhookData = {
2126
+ event_type,
2127
+ team_id,
2128
+ endpoint,
2129
+ passcode,
2130
+ };
2131
+
2132
+ if (description) {
2133
+ webhookData.description = description;
2134
+ }
2135
+
2136
+ const result = await createWebhook(webhookData);
2137
+
2138
+ return {
2139
+ content: [
2140
+ {
2141
+ type: 'text',
2142
+ text: `Webhook created successfully!\n\n${JSON.stringify(result, null, 2)}`,
2143
+ },
2144
+ ],
2145
+ };
2146
+ }
2147
+
2148
+ if (name === 'get_webhook') {
2149
+ const { webhook_id } = args;
2150
+
2151
+ if (!webhook_id) {
2152
+ throw new Error('webhook_id is required');
2153
+ }
2154
+
2155
+ const result = await getWebhook(webhook_id);
2156
+
2157
+ return {
2158
+ content: [
2159
+ {
2160
+ type: 'text',
2161
+ text: JSON.stringify(result, null, 2),
2162
+ },
2163
+ ],
2164
+ };
2165
+ }
2166
+
2167
+ if (name === 'get_webhooks') {
2168
+ const { team_id } = args;
2169
+
2170
+ const result = await getWebhooks(team_id || null);
2171
+
2172
+ return {
2173
+ content: [
2174
+ {
2175
+ type: 'text',
2176
+ text: JSON.stringify(result, null, 2),
2177
+ },
2178
+ ],
2179
+ };
2180
+ }
2181
+
2182
+ if (name === 'update_webhook') {
2183
+ const { webhook_id, status, description, endpoint, passcode } = args;
2184
+
2185
+ if (!webhook_id) {
2186
+ throw new Error('webhook_id is required');
2187
+ }
2188
+
2189
+ const updateData = {};
2190
+ if (status) updateData.status = status;
2191
+ if (description) updateData.description = description;
2192
+ if (endpoint) updateData.endpoint = endpoint;
2193
+ if (passcode) updateData.passcode = passcode;
2194
+
2195
+ const result = await updateWebhook(webhook_id, updateData);
2196
+
2197
+ return {
2198
+ content: [
2199
+ {
2200
+ type: 'text',
2201
+ text: `Webhook updated successfully!\n\n${JSON.stringify(result, null, 2)}`,
2202
+ },
2203
+ ],
2204
+ };
2205
+ }
2206
+
2207
+ if (name === 'delete_webhook') {
2208
+ const { webhook_id } = args;
2209
+
2210
+ if (!webhook_id) {
2211
+ throw new Error('webhook_id is required');
2212
+ }
2213
+
2214
+ const result = await deleteWebhook(webhook_id);
2215
+
2216
+ return {
2217
+ content: [
2218
+ {
2219
+ type: 'text',
2220
+ text: `${result.message}\n\n${JSON.stringify(result, null, 2)}`,
2221
+ },
2222
+ ],
2223
+ };
2224
+ }
2225
+
2226
+ if (name === 'get_webhook_requests') {
2227
+ const { webhook_id } = args;
2228
+
2229
+ if (!webhook_id) {
2230
+ throw new Error('webhook_id is required');
2231
+ }
2232
+
2233
+ const result = await getWebhookRequests(webhook_id);
2234
+
2235
+ return {
2236
+ content: [
2237
+ {
2238
+ type: 'text',
2239
+ text: JSON.stringify(result, null, 2),
2240
  },
2241
  ],
2242
  };
package.json CHANGED
@@ -1,7 +1,7 @@
1
  {
2
  "name": "mcp-figma-comment-summary",
3
- "version": "1.2.1",
4
- "description": "MCP server to retrieve, create, and analyze Figma file comments with @mentions - enables Claude Desktop to read and write design feedback with proper user notifications",
5
  "type": "module",
6
  "main": "index.js",
7
  "bin": {
 
1
  {
2
  "name": "mcp-figma-comment-summary",
3
+ "version": "2.0.0",
4
+ "description": "Complete MCP server for Figma REST API - access files, comments, variables, webhooks, projects, teams, dev resources, and more through Claude Desktop",
5
  "type": "module",
6
  "main": "index.js",
7
  "bin": {