figmanage 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +338 -0
- package/dist/auth/client.d.ts +15 -0
- package/dist/auth/client.js +29 -0
- package/dist/auth/health.d.ts +5 -0
- package/dist/auth/health.js +93 -0
- package/dist/clients/internal-api.d.ts +4 -0
- package/dist/clients/internal-api.js +50 -0
- package/dist/clients/public-api.d.ts +4 -0
- package/dist/clients/public-api.js +47 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +100 -0
- package/dist/setup.d.ts +3 -0
- package/dist/setup.js +565 -0
- package/dist/tools/analytics.d.ts +2 -0
- package/dist/tools/analytics.js +58 -0
- package/dist/tools/branching.d.ts +2 -0
- package/dist/tools/branching.js +103 -0
- package/dist/tools/comments.d.ts +2 -0
- package/dist/tools/comments.js +147 -0
- package/dist/tools/components.d.ts +2 -0
- package/dist/tools/components.js +104 -0
- package/dist/tools/compound.d.ts +2 -0
- package/dist/tools/compound.js +334 -0
- package/dist/tools/export.d.ts +2 -0
- package/dist/tools/export.js +70 -0
- package/dist/tools/files.d.ts +2 -0
- package/dist/tools/files.js +241 -0
- package/dist/tools/libraries.d.ts +2 -0
- package/dist/tools/libraries.js +31 -0
- package/dist/tools/navigate.d.ts +2 -0
- package/dist/tools/navigate.js +436 -0
- package/dist/tools/org.d.ts +2 -0
- package/dist/tools/org.js +311 -0
- package/dist/tools/permissions.d.ts +2 -0
- package/dist/tools/permissions.js +246 -0
- package/dist/tools/projects.d.ts +2 -0
- package/dist/tools/projects.js +160 -0
- package/dist/tools/reading.d.ts +2 -0
- package/dist/tools/reading.js +60 -0
- package/dist/tools/register.d.ts +32 -0
- package/dist/tools/register.js +76 -0
- package/dist/tools/teams.d.ts +2 -0
- package/dist/tools/teams.js +81 -0
- package/dist/tools/variables.d.ts +2 -0
- package/dist/tools/variables.js +102 -0
- package/dist/tools/versions.d.ts +2 -0
- package/dist/tools/versions.js +69 -0
- package/dist/tools/webhooks.d.ts +2 -0
- package/dist/tools/webhooks.js +126 -0
- package/dist/types/figma.d.ts +58 -0
- package/dist/types/figma.js +2 -0
- package/package.json +55 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Danny Keane
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
# figmanage
|
|
2
|
+
|
|
3
|
+
MCP server for managing your Figma workspace.
|
|
4
|
+
|
|
5
|
+
Manages Figma workspaces through AI assistants. 76 tools covering files, projects, teams, permissions, comments, versions, components, webhooks, org admin, and more. Works with Claude Code, Claude Desktop, ChatGPT, and any MCP-compatible client.
|
|
6
|
+
|
|
7
|
+
## quick start
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx figmanage # starts MCP server (stdio)
|
|
11
|
+
npx figmanage --http 3333 # starts MCP server (HTTP, for ChatGPT etc.)
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## setup
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
git clone https://github.com/dannykeane/figmanage.git
|
|
18
|
+
cd figmanage
|
|
19
|
+
npm install
|
|
20
|
+
npm run build
|
|
21
|
+
npm run setup # extracts Chrome cookie, configures your MCP client
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The setup script:
|
|
25
|
+
- Reads your Figma session cookie from Chrome (macOS, Linux, Windows)
|
|
26
|
+
- Detects your org ID via Figma's redirect behavior
|
|
27
|
+
- Prompts for a Personal Access Token
|
|
28
|
+
- Stores all credentials in MCP server config (env vars)
|
|
29
|
+
- Auto-detects Claude Code, or prints config for other clients
|
|
30
|
+
|
|
31
|
+
## configuration
|
|
32
|
+
|
|
33
|
+
| Env var | Description |
|
|
34
|
+
|---------|-------------|
|
|
35
|
+
| `FIGMA_PAT` | Personal access token |
|
|
36
|
+
| `FIGMA_AUTH_COOKIE` | Session cookie value |
|
|
37
|
+
| `FIGMA_USER_ID` | Your Figma user ID |
|
|
38
|
+
| `FIGMA_ORG_ID` | Your org ID |
|
|
39
|
+
| `FIGMA_TOOLSETS` | Toolset filter or preset name |
|
|
40
|
+
| `FIGMA_READ_ONLY` | Set to `1` to disable mutations |
|
|
41
|
+
|
|
42
|
+
The setup script stores credentials as env vars in the MCP server config. You can also set them manually.
|
|
43
|
+
|
|
44
|
+
### toolset presets
|
|
45
|
+
|
|
46
|
+
Use `FIGMA_TOOLSETS` to expose only the tool groups you need. Presets bundle common combinations:
|
|
47
|
+
|
|
48
|
+
| Preset | Toolsets included |
|
|
49
|
+
|--------|-------------------|
|
|
50
|
+
| `starter` | navigate, reading, comments, export |
|
|
51
|
+
| `admin` | navigate, org, permissions, analytics, teams, libraries |
|
|
52
|
+
| `readonly` | navigate, reading, comments, export, components, versions |
|
|
53
|
+
| `full` | everything (default) |
|
|
54
|
+
|
|
55
|
+
Or pass individual toolsets:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
FIGMA_TOOLSETS=navigate,reading,comments
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Available toolsets: `navigate`, `files`, `projects`, `permissions`, `org`, `versions`, `branching`, `comments`, `export`, `analytics`, `reading`, `components`, `webhooks`, `variables`, `compound`, `teams`, `libraries`.
|
|
62
|
+
|
|
63
|
+
### multi-org support
|
|
64
|
+
|
|
65
|
+
`list_orgs` discovers all available orgs. `switch_org` changes the active workspace for the session. 12 tools accept an `org_id` override parameter for one-off cross-org queries without switching.
|
|
66
|
+
|
|
67
|
+
## auth
|
|
68
|
+
|
|
69
|
+
figmanage uses two API clients targeting different Figma endpoints:
|
|
70
|
+
|
|
71
|
+
| Client | Base URL | Auth | Capabilities |
|
|
72
|
+
|--------|----------|------|-------------|
|
|
73
|
+
| Internal API | `www.figma.com` | Session cookie | Workspace management, search, permissions, org admin, file lifecycle |
|
|
74
|
+
| Public API | `api.figma.com` | Personal Access Token | Comments, export, file reading, components, versions, webhooks, variables |
|
|
75
|
+
|
|
76
|
+
Each tool declares its auth requirement:
|
|
77
|
+
|
|
78
|
+
| Requirement | Meaning |
|
|
79
|
+
|-------------|---------|
|
|
80
|
+
| `cookie` | Internal API only. Needs `FIGMA_AUTH_COOKIE` + `FIGMA_USER_ID`. |
|
|
81
|
+
| `pat` | Public API only. Needs `FIGMA_PAT`. |
|
|
82
|
+
| `either` | Works with whichever auth is available. Prefers public API when PAT present. |
|
|
83
|
+
|
|
84
|
+
Tools are automatically filtered at startup based on available credentials. If you only have a PAT, cookie-only tools are not registered.
|
|
85
|
+
|
|
86
|
+
## tools
|
|
87
|
+
|
|
88
|
+
### navigate (10 tools)
|
|
89
|
+
|
|
90
|
+
| Tool | Auth | Description |
|
|
91
|
+
|------|------|-------------|
|
|
92
|
+
| `check_auth` | either | Validate PAT and cookie authentication |
|
|
93
|
+
| `list_orgs` | cookie | List available Figma workspaces (orgs) with names |
|
|
94
|
+
| `switch_org` | cookie | Switch active workspace for this session |
|
|
95
|
+
| `list_teams` | cookie | List teams in your org |
|
|
96
|
+
| `list_projects` | either | List projects in a team |
|
|
97
|
+
| `list_files` | either | List files in a project (paginated) |
|
|
98
|
+
| `list_recent_files` | cookie | Recently viewed/edited files across teams |
|
|
99
|
+
| `search` | cookie | Search files across the workspace |
|
|
100
|
+
| `get_file_info` | either | File metadata: name, project, team, link access |
|
|
101
|
+
| `list_favorites` | cookie | List favorited files (broken -- see known limitations) |
|
|
102
|
+
|
|
103
|
+
### files (8 tools)
|
|
104
|
+
|
|
105
|
+
| Tool | Auth | Description |
|
|
106
|
+
|------|------|-------------|
|
|
107
|
+
| `create_file` | cookie | Create design, whiteboard, slides, or sites file |
|
|
108
|
+
| `rename_file` | cookie | Rename a file |
|
|
109
|
+
| `move_files` | cookie | Move files between projects (batch) |
|
|
110
|
+
| `duplicate_file` | cookie | Copy a file, optionally to another project |
|
|
111
|
+
| `trash_files` | cookie | Move files to trash (batch) |
|
|
112
|
+
| `restore_files` | cookie | Restore files from trash (batch) |
|
|
113
|
+
| `favorite_file` | cookie | Add/remove file from sidebar favorites |
|
|
114
|
+
| `set_link_access` | cookie | Set link sharing level (inherit/view/edit/org_view/org_edit) |
|
|
115
|
+
|
|
116
|
+
### projects (6 tools)
|
|
117
|
+
|
|
118
|
+
| Tool | Auth | Description |
|
|
119
|
+
|------|------|-------------|
|
|
120
|
+
| `create_project` | cookie | Create a project in a team |
|
|
121
|
+
| `rename_project` | cookie | Rename a project |
|
|
122
|
+
| `move_project` | cookie | Move a project to another team |
|
|
123
|
+
| `trash_project` | cookie | Move a project to trash |
|
|
124
|
+
| `restore_project` | cookie | Restore a project from trash |
|
|
125
|
+
| `set_project_description` | cookie | Set or update project description |
|
|
126
|
+
|
|
127
|
+
### permissions (7 tools)
|
|
128
|
+
|
|
129
|
+
| Tool | Auth | Description |
|
|
130
|
+
|------|------|-------------|
|
|
131
|
+
| `get_permissions` | cookie | List who has access to a file/project/team with roles |
|
|
132
|
+
| `set_permissions` | cookie | Change a user's access level |
|
|
133
|
+
| `share` | cookie | Invite someone by email (viewer or editor) |
|
|
134
|
+
| `revoke_access` | cookie | Remove someone's access |
|
|
135
|
+
| `list_role_requests` | cookie | List pending file access requests |
|
|
136
|
+
| `approve_role_request` | cookie | Accept an access request |
|
|
137
|
+
| `deny_role_request` | cookie | Decline an access request |
|
|
138
|
+
|
|
139
|
+
### reading (2 tools)
|
|
140
|
+
|
|
141
|
+
| Tool | Auth | Description |
|
|
142
|
+
|------|------|-------------|
|
|
143
|
+
| `get_file` | pat | Read file contents as a node tree with depth control |
|
|
144
|
+
| `get_nodes` | pat | Read specific nodes by ID from a file |
|
|
145
|
+
|
|
146
|
+
### components (4 tools)
|
|
147
|
+
|
|
148
|
+
| Tool | Auth | Description |
|
|
149
|
+
|------|------|-------------|
|
|
150
|
+
| `list_file_components` | pat | List components published from a file |
|
|
151
|
+
| `list_file_styles` | pat | List styles in a file |
|
|
152
|
+
| `list_team_components` | pat | List published components across a team (paginated) |
|
|
153
|
+
| `list_team_styles` | pat | List published styles across a team (paginated) |
|
|
154
|
+
|
|
155
|
+
### versions (2 tools)
|
|
156
|
+
|
|
157
|
+
| Tool | Auth | Description |
|
|
158
|
+
|------|------|-------------|
|
|
159
|
+
| `list_versions` | pat | List version history (named checkpoints and auto-saves) |
|
|
160
|
+
| `create_version` | cookie | Create a named version checkpoint |
|
|
161
|
+
|
|
162
|
+
### branching (3 tools)
|
|
163
|
+
|
|
164
|
+
| Tool | Auth | Description |
|
|
165
|
+
|------|------|-------------|
|
|
166
|
+
| `list_branches` | either | List branches of a file |
|
|
167
|
+
| `create_branch` | cookie | Create a branch from a file |
|
|
168
|
+
| `delete_branch` | cookie | Archive (delete) a branch |
|
|
169
|
+
|
|
170
|
+
### comments (4 tools)
|
|
171
|
+
|
|
172
|
+
| Tool | Auth | Description |
|
|
173
|
+
|------|------|-------------|
|
|
174
|
+
| `list_comments` | pat | List comments with thread structure (optional markdown format) |
|
|
175
|
+
| `post_comment` | pat | Post a comment, optionally pinned to a node or as a reply |
|
|
176
|
+
| `delete_comment` | pat | Permanently delete a comment |
|
|
177
|
+
| `list_comment_reactions` | pat | List emoji reactions on a comment |
|
|
178
|
+
|
|
179
|
+
### export (2 tools)
|
|
180
|
+
|
|
181
|
+
| Tool | Auth | Description |
|
|
182
|
+
|------|------|-------------|
|
|
183
|
+
| `export_nodes` | pat | Export nodes as PNG, SVG, PDF, or JPG (returns temporary URLs) |
|
|
184
|
+
| `get_image_fills` | pat | Get URLs for all images used as fills in a file |
|
|
185
|
+
|
|
186
|
+
### webhooks (4 tools)
|
|
187
|
+
|
|
188
|
+
| Tool | Auth | Description |
|
|
189
|
+
|------|------|-------------|
|
|
190
|
+
| `list_webhooks` | pat | List webhooks for a team |
|
|
191
|
+
| `create_webhook` | pat | Create a webhook subscription for a team |
|
|
192
|
+
| `update_webhook` | pat | Update a webhook (endpoint, event type, status) |
|
|
193
|
+
| `delete_webhook` | pat | Delete a webhook |
|
|
194
|
+
|
|
195
|
+
### variables (3 tools, Enterprise only)
|
|
196
|
+
|
|
197
|
+
| Tool | Auth | Description |
|
|
198
|
+
|------|------|-------------|
|
|
199
|
+
| `list_local_variables` | pat | List local variables and collections in a file |
|
|
200
|
+
| `list_published_variables` | pat | List published variables from a library file |
|
|
201
|
+
| `update_variables` | pat | Bulk create, update, or delete variables, collections, modes, and values |
|
|
202
|
+
|
|
203
|
+
### analytics (2 tools)
|
|
204
|
+
|
|
205
|
+
| Tool | Auth | Description |
|
|
206
|
+
|------|------|-------------|
|
|
207
|
+
| `library_usage` | cookie | Team-level library adoption metrics over a lookback period |
|
|
208
|
+
| `component_usage` | cookie | Per-file component usage analytics |
|
|
209
|
+
|
|
210
|
+
### org (9 tools)
|
|
211
|
+
|
|
212
|
+
| Tool | Auth | Description |
|
|
213
|
+
|------|------|-------------|
|
|
214
|
+
| `list_admins` | cookie | Org admins with permission levels and seat status |
|
|
215
|
+
| `list_org_teams` | cookie | All teams with member counts, project counts, access levels |
|
|
216
|
+
| `seat_usage` | cookie | Seat breakdown: permissions, seat types, activity, account types |
|
|
217
|
+
| `list_team_members` | cookie | Team members with name, email, role, last active date |
|
|
218
|
+
| `billing_overview` | cookie | Invoice history, billing status, amounts, billing periods |
|
|
219
|
+
| `list_invoices` | cookie | Open and upcoming invoices |
|
|
220
|
+
| `org_domains` | cookie | Domain configuration and SSO/SAML settings |
|
|
221
|
+
| `ai_credit_usage` | cookie | AI credit usage summary for a billing plan |
|
|
222
|
+
| `export_members` | cookie | Trigger async CSV export of all org members (emailed to admin) |
|
|
223
|
+
|
|
224
|
+
### teams (3 tools)
|
|
225
|
+
|
|
226
|
+
| Tool | Auth | Description |
|
|
227
|
+
|------|------|-------------|
|
|
228
|
+
| `create_team` | cookie | Create a new team in the org |
|
|
229
|
+
| `rename_team` | cookie | Rename an existing team |
|
|
230
|
+
| `delete_team` | cookie | Delete a team (destructive, cannot be undone) |
|
|
231
|
+
|
|
232
|
+
### libraries (1 tool)
|
|
233
|
+
|
|
234
|
+
| Tool | Auth | Description |
|
|
235
|
+
|------|------|-------------|
|
|
236
|
+
| `list_org_libraries` | cookie | All design system libraries in the org with sharing group info |
|
|
237
|
+
|
|
238
|
+
### compound (6 tools)
|
|
239
|
+
|
|
240
|
+
Multi-step operations that aggregate data from several API calls.
|
|
241
|
+
|
|
242
|
+
| Tool | Auth | Description |
|
|
243
|
+
|------|------|-------------|
|
|
244
|
+
| `file_summary` | pat | Quick overview: pages, components, styles, comment counts |
|
|
245
|
+
| `workspace_overview` | cookie | Full org snapshot: teams, seats, billing in one call |
|
|
246
|
+
| `open_comments` | pat | Aggregated unresolved comments across all files in a project |
|
|
247
|
+
| `cleanup_stale_files` | either | Find files not modified in N days, optionally trash them (dry run by default) |
|
|
248
|
+
| `organize_project` | cookie | Batch-move files into a target project |
|
|
249
|
+
| `setup_project_structure` | cookie | Create multiple projects in a team from a plan |
|
|
250
|
+
|
|
251
|
+
## transport modes
|
|
252
|
+
|
|
253
|
+
| Mode | Flag | Use case |
|
|
254
|
+
|------|------|----------|
|
|
255
|
+
| stdio | (default) | Claude Code, Claude Desktop, most MCP clients |
|
|
256
|
+
| HTTP | `--http <port>` | ChatGPT, web-based clients |
|
|
257
|
+
|
|
258
|
+
```bash
|
|
259
|
+
npx figmanage # stdio
|
|
260
|
+
npx figmanage --http 3333 # HTTP on port 3333
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
## security
|
|
264
|
+
|
|
265
|
+
### ID validation
|
|
266
|
+
|
|
267
|
+
All tool parameters that accept Figma IDs (file keys, team IDs, project IDs, node IDs) are validated against a strict regex (`/^[\w.:-]+$/`) before use. This blocks path traversal in URL templates.
|
|
268
|
+
|
|
269
|
+
### safe retry policy
|
|
270
|
+
|
|
271
|
+
Both API clients retry on transient errors with exponential backoff. Rate limit responses (429) are only retried for safe methods (`GET`, `HEAD`, `OPTIONS`). Mutations are never retried on 429 to prevent duplicate writes. Auth failures (401, 403) are never retried.
|
|
272
|
+
|
|
273
|
+
### response shaping
|
|
274
|
+
|
|
275
|
+
Org admin tools extract documented fields and strip PII (e.g., `shipping_address` is removed from billing responses).
|
|
276
|
+
|
|
277
|
+
## architecture
|
|
278
|
+
|
|
279
|
+
```
|
|
280
|
+
src/
|
|
281
|
+
index.ts MCP server entry point (stdio transport, toolset parsing)
|
|
282
|
+
setup.ts Chrome cookie extraction + Claude MCP config
|
|
283
|
+
auth/
|
|
284
|
+
client.ts AuthConfig, env var loading
|
|
285
|
+
health.ts Auth validation
|
|
286
|
+
clients/
|
|
287
|
+
internal-api.ts Axios client for www.figma.com (cookie auth, safe retry)
|
|
288
|
+
public-api.ts Axios client for api.figma.com (PAT auth, safe retry)
|
|
289
|
+
tools/
|
|
290
|
+
register.ts defineTool() pattern, toolset filtering, read-only mode, figmaId validation
|
|
291
|
+
navigate.ts Browse and search (10 tools)
|
|
292
|
+
files.ts File lifecycle (8 tools)
|
|
293
|
+
projects.ts Project management (6 tools)
|
|
294
|
+
permissions.ts Sharing, access control, role requests (7 tools)
|
|
295
|
+
reading.ts File and node reading via public API (2 tools)
|
|
296
|
+
components.ts Components and styles via public API (4 tools)
|
|
297
|
+
versions.ts Version history (2 tools)
|
|
298
|
+
branching.ts Branch CRUD (3 tools)
|
|
299
|
+
comments.ts Comments and reactions via public API (4 tools)
|
|
300
|
+
export.ts Image export and fills via public API (2 tools)
|
|
301
|
+
webhooks.ts Webhook CRUD via V2 public API (4 tools)
|
|
302
|
+
variables.ts Variables via public API, Enterprise only (3 tools)
|
|
303
|
+
analytics.ts Library and component analytics via internal API (2 tools)
|
|
304
|
+
org.ts Org admin, billing, seats, domains via internal API (9 tools)
|
|
305
|
+
teams.ts Team CRUD (3 tools)
|
|
306
|
+
libraries.ts Design system libraries via internal API (1 tool)
|
|
307
|
+
compound.ts Multi-step aggregation tools (6 tools)
|
|
308
|
+
types/
|
|
309
|
+
figma.ts Shared types (Toolset, AuthConfig, etc.)
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
Tools self-register via `defineTool()` side-effect imports. Each tool declares its toolset, auth requirement, and whether it mutates. `registerTools()` filters at startup based on available credentials, enabled toolsets, and read-only mode.
|
|
313
|
+
|
|
314
|
+
## known limitations
|
|
315
|
+
|
|
316
|
+
- **list_favorites**: Figma's server has a BigInt overflow bug on large user IDs. The `favorite_file` tool for adding/removing favorites works fine.
|
|
317
|
+
- **Branch merging**: Figma merges branches through its real-time multiplayer protocol, not a REST endpoint. Must be done in the Figma UI.
|
|
318
|
+
- **Cookie expiry**: Session cookies expire roughly every 30 days. Re-run `npm run setup` to refresh.
|
|
319
|
+
- **Windows cookie extraction**: Best-effort via DPAPI/PowerShell. Falls back to PAT-only setup if extraction fails.
|
|
320
|
+
- **Variables require Enterprise**: The `file_variables:read` and `file_variables:write` scopes are not available in the standard PAT UI. Variables tools return a clear error on non-Enterprise plans.
|
|
321
|
+
- **No org-level member list**: There is no single REST endpoint to list all org members. Use `list_admins` + `list_team_members` per team, or `export_members` for a full CSV.
|
|
322
|
+
- **No activity log API**: Figma's activity log is SSR only with no REST endpoint.
|
|
323
|
+
- **get_file returns full tree**: Without a `depth` parameter, `get_file` returns the entire document. Use `depth` or `get_nodes` for large files.
|
|
324
|
+
|
|
325
|
+
## development
|
|
326
|
+
|
|
327
|
+
```bash
|
|
328
|
+
npm install
|
|
329
|
+
npm run build # compile
|
|
330
|
+
npm run setup # configure auth + MCP client
|
|
331
|
+
npm run dev # watch mode
|
|
332
|
+
npm run lint # type-check
|
|
333
|
+
npm test # run tests
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
## license
|
|
337
|
+
|
|
338
|
+
MIT
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface OrgEntry {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
}
|
|
5
|
+
export interface AuthConfig {
|
|
6
|
+
pat?: string;
|
|
7
|
+
cookie?: string;
|
|
8
|
+
userId?: string;
|
|
9
|
+
orgId?: string;
|
|
10
|
+
orgs?: OrgEntry[];
|
|
11
|
+
}
|
|
12
|
+
export declare function loadAuthConfig(): AuthConfig;
|
|
13
|
+
export declare function hasPat(config: AuthConfig): boolean;
|
|
14
|
+
export declare function hasCookie(config: AuthConfig): boolean;
|
|
15
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
function parseOrgs(raw) {
|
|
2
|
+
if (!raw)
|
|
3
|
+
return undefined;
|
|
4
|
+
try {
|
|
5
|
+
const parsed = JSON.parse(raw);
|
|
6
|
+
if (!Array.isArray(parsed))
|
|
7
|
+
return undefined;
|
|
8
|
+
return parsed.filter((o) => o.id && o.name);
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export function loadAuthConfig() {
|
|
15
|
+
return {
|
|
16
|
+
pat: process.env.FIGMA_PAT,
|
|
17
|
+
cookie: process.env.FIGMA_AUTH_COOKIE,
|
|
18
|
+
userId: process.env.FIGMA_USER_ID,
|
|
19
|
+
orgId: process.env.FIGMA_ORG_ID,
|
|
20
|
+
orgs: parseOrgs(process.env.FIGMA_ORGS),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export function hasPat(config) {
|
|
24
|
+
return !!config.pat;
|
|
25
|
+
}
|
|
26
|
+
export function hasCookie(config) {
|
|
27
|
+
return !!config.cookie && !!config.userId;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { AuthConfig } from './client.js';
|
|
2
|
+
import type { AuthStatus } from '../types/figma.js';
|
|
3
|
+
export declare function checkAuth(config: AuthConfig): Promise<AuthStatus>;
|
|
4
|
+
export declare function formatAuthStatus(status: AuthStatus, config?: AuthConfig): string;
|
|
5
|
+
//# sourceMappingURL=health.d.ts.map
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { publicClient } from '../clients/public-api.js';
|
|
2
|
+
import { internalClient } from '../clients/internal-api.js';
|
|
3
|
+
export async function checkAuth(config) {
|
|
4
|
+
const status = {
|
|
5
|
+
pat: { valid: false },
|
|
6
|
+
cookie: { valid: false },
|
|
7
|
+
};
|
|
8
|
+
if (config.pat) {
|
|
9
|
+
try {
|
|
10
|
+
const res = await publicClient(config).get('/v1/me');
|
|
11
|
+
status.pat = { valid: true, user: res.data.handle || res.data.email };
|
|
12
|
+
}
|
|
13
|
+
catch (e) {
|
|
14
|
+
status.pat = {
|
|
15
|
+
valid: false,
|
|
16
|
+
error: e.response?.status === 403
|
|
17
|
+
? 'PAT invalid or expired. Generate a new one at figma.com/developers'
|
|
18
|
+
: `PAT check failed: ${e.message}`,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
status.pat = { valid: false, error: 'No PAT configured (env or Keychain)' };
|
|
24
|
+
}
|
|
25
|
+
if (config.cookie && config.userId) {
|
|
26
|
+
try {
|
|
27
|
+
const res = await internalClient(config).get('/api/user/state');
|
|
28
|
+
const user = res.data?.user;
|
|
29
|
+
status.cookie = { valid: true, user: user?.handle || user?.email || 'authenticated' };
|
|
30
|
+
// Populate org registry from user/state response
|
|
31
|
+
const orgs = (res.data?.meta?.orgs || []).map((o) => ({
|
|
32
|
+
id: String(o.id),
|
|
33
|
+
name: o.name,
|
|
34
|
+
}));
|
|
35
|
+
if (orgs.length > 0)
|
|
36
|
+
config.orgs = orgs;
|
|
37
|
+
}
|
|
38
|
+
catch (e) {
|
|
39
|
+
const code = e.response?.status;
|
|
40
|
+
if (code === 401 || code === 403) {
|
|
41
|
+
status.cookie = {
|
|
42
|
+
valid: false,
|
|
43
|
+
error: 'Session cookie expired. Extract a new one from browser DevTools: Application > Cookies > __Host-figma.authn',
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
status.cookie = { valid: false, error: `Cookie check failed: ${e.message}` };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
const missing = [];
|
|
53
|
+
if (!config.cookie)
|
|
54
|
+
missing.push('FIGMA_AUTH_COOKIE');
|
|
55
|
+
if (!config.userId)
|
|
56
|
+
missing.push('FIGMA_USER_ID');
|
|
57
|
+
status.cookie = { valid: false, error: `${missing.join(', ')} not set` };
|
|
58
|
+
}
|
|
59
|
+
return status;
|
|
60
|
+
}
|
|
61
|
+
export function formatAuthStatus(status, config) {
|
|
62
|
+
const lines = [];
|
|
63
|
+
lines.push(`PAT: ${status.pat.valid ? `valid (${status.pat.user})` : status.pat.error}`);
|
|
64
|
+
lines.push(`Session: ${status.cookie.valid ? `valid (${status.cookie.user})` : status.cookie.error}`);
|
|
65
|
+
if (!status.pat.valid && !status.cookie.valid) {
|
|
66
|
+
lines.push('\nNo valid auth. Set FIGMA_PAT for public API access or FIGMA_AUTH_COOKIE + FIGMA_USER_ID for full access.');
|
|
67
|
+
}
|
|
68
|
+
else if (!status.cookie.valid) {
|
|
69
|
+
lines.push('\nPublic API only. Internal API tools (file CRUD, team management) unavailable.');
|
|
70
|
+
}
|
|
71
|
+
// Org context
|
|
72
|
+
if (config) {
|
|
73
|
+
if (config.orgId) {
|
|
74
|
+
const currentOrg = config.orgs?.find(o => o.id === config.orgId);
|
|
75
|
+
const orgLabel = currentOrg ? `${currentOrg.name} (${config.orgId})` : config.orgId;
|
|
76
|
+
lines.push(`\nWorkspace: ${orgLabel}`);
|
|
77
|
+
const others = (config.orgs || []).filter(o => o.id !== config.orgId);
|
|
78
|
+
if (others.length > 0) {
|
|
79
|
+
lines.push(`Other workspaces: ${others.map(o => `${o.name} (${o.id})`).join(', ')}`);
|
|
80
|
+
lines.push('Use switch_org to change workspace.');
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
else if (config.orgs && config.orgs.length > 0) {
|
|
84
|
+
lines.push(`\nWorkspaces available: ${config.orgs.map(o => `${o.name} (${o.id})`).join(', ')}`);
|
|
85
|
+
lines.push('Use switch_org to select a workspace.');
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
lines.push('\nWorkspace: not configured');
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return lines.join('\n');
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=health.js.map
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import axiosRetry from 'axios-retry';
|
|
3
|
+
import { Agent as HttpsAgent } from 'https';
|
|
4
|
+
const httpsAgent = new HttpsAgent({ keepAlive: true });
|
|
5
|
+
const instances = new WeakMap();
|
|
6
|
+
export function internalClient(config) {
|
|
7
|
+
const existing = instances.get(config);
|
|
8
|
+
if (existing)
|
|
9
|
+
return existing;
|
|
10
|
+
const client = axios.create({
|
|
11
|
+
baseURL: 'https://www.figma.com',
|
|
12
|
+
httpsAgent,
|
|
13
|
+
headers: {
|
|
14
|
+
'Cookie': `__Host-figma.authn=${config.cookie || ''}`,
|
|
15
|
+
'X-CSRF-Bypass': 'yes',
|
|
16
|
+
'X-Figma-User-Id': config.userId || '',
|
|
17
|
+
'Content-Type': 'application/json',
|
|
18
|
+
'Accept': 'application/json',
|
|
19
|
+
},
|
|
20
|
+
timeout: 30000,
|
|
21
|
+
});
|
|
22
|
+
axiosRetry(client, {
|
|
23
|
+
retries: 3,
|
|
24
|
+
retryDelay: (retryCount, error) => {
|
|
25
|
+
const retryAfter = error.response?.headers?.['retry-after'];
|
|
26
|
+
if (retryAfter) {
|
|
27
|
+
const delay = parseInt(retryAfter, 10) * 1000;
|
|
28
|
+
if (delay > 0 && delay < 300000)
|
|
29
|
+
return delay;
|
|
30
|
+
}
|
|
31
|
+
// Exponential backoff with jitter: 1s, 2s, 4s + random jitter
|
|
32
|
+
const base = Math.pow(2, retryCount - 1) * 1000;
|
|
33
|
+
const jitter = base * 0.5 * Math.random();
|
|
34
|
+
return base + jitter;
|
|
35
|
+
},
|
|
36
|
+
retryCondition: (error) => {
|
|
37
|
+
const status = error.response?.status;
|
|
38
|
+
if (status === 401 || status === 403)
|
|
39
|
+
return false;
|
|
40
|
+
if (status === 429) {
|
|
41
|
+
const method = error.config?.method?.toUpperCase();
|
|
42
|
+
return ['GET', 'HEAD', 'OPTIONS'].includes(method || '');
|
|
43
|
+
}
|
|
44
|
+
return axiosRetry.isNetworkOrIdempotentRequestError(error);
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
instances.set(config, client);
|
|
48
|
+
return client;
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=internal-api.js.map
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import axiosRetry from 'axios-retry';
|
|
3
|
+
import { Agent as HttpsAgent } from 'https';
|
|
4
|
+
const httpsAgent = new HttpsAgent({ keepAlive: true });
|
|
5
|
+
const instances = new WeakMap();
|
|
6
|
+
export function publicClient(config) {
|
|
7
|
+
const existing = instances.get(config);
|
|
8
|
+
if (existing)
|
|
9
|
+
return existing;
|
|
10
|
+
const client = axios.create({
|
|
11
|
+
baseURL: 'https://api.figma.com',
|
|
12
|
+
httpsAgent,
|
|
13
|
+
headers: {
|
|
14
|
+
'X-Figma-Token': config.pat || '',
|
|
15
|
+
'Accept': 'application/json',
|
|
16
|
+
},
|
|
17
|
+
timeout: 30000,
|
|
18
|
+
});
|
|
19
|
+
axiosRetry(client, {
|
|
20
|
+
retries: 3,
|
|
21
|
+
retryDelay: (retryCount, error) => {
|
|
22
|
+
const retryAfter = error.response?.headers?.['retry-after'];
|
|
23
|
+
if (retryAfter) {
|
|
24
|
+
const delay = parseInt(retryAfter, 10) * 1000;
|
|
25
|
+
if (delay > 0 && delay < 300000)
|
|
26
|
+
return delay;
|
|
27
|
+
}
|
|
28
|
+
// Exponential backoff with jitter: 1s, 2s, 4s + random jitter
|
|
29
|
+
const base = Math.pow(2, retryCount - 1) * 1000;
|
|
30
|
+
const jitter = base * 0.5 * Math.random();
|
|
31
|
+
return base + jitter;
|
|
32
|
+
},
|
|
33
|
+
retryCondition: (error) => {
|
|
34
|
+
const status = error.response?.status;
|
|
35
|
+
if (status === 401 || status === 403)
|
|
36
|
+
return false;
|
|
37
|
+
if (status === 429) {
|
|
38
|
+
const method = error.config?.method?.toUpperCase();
|
|
39
|
+
return ['GET', 'HEAD', 'OPTIONS'].includes(method || '');
|
|
40
|
+
}
|
|
41
|
+
return axiosRetry.isNetworkOrIdempotentRequestError(error);
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
instances.set(config, client);
|
|
45
|
+
return client;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=public-api.js.map
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import './tools/navigate.js';
|
|
3
|
+
import './tools/files.js';
|
|
4
|
+
import './tools/projects.js';
|
|
5
|
+
import './tools/permissions.js';
|
|
6
|
+
import './tools/comments.js';
|
|
7
|
+
import './tools/export.js';
|
|
8
|
+
import './tools/versions.js';
|
|
9
|
+
import './tools/branching.js';
|
|
10
|
+
import './tools/components.js';
|
|
11
|
+
import './tools/webhooks.js';
|
|
12
|
+
import './tools/reading.js';
|
|
13
|
+
import './tools/analytics.js';
|
|
14
|
+
import './tools/variables.js';
|
|
15
|
+
import './tools/org.js';
|
|
16
|
+
import './tools/libraries.js';
|
|
17
|
+
import './tools/teams.js';
|
|
18
|
+
import './tools/compound.js';
|
|
19
|
+
//# sourceMappingURL=index.d.ts.map
|