@mp3wizard/figma-console-mcp 1.17.3 → 1.19.2
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/README.md +13 -12
- package/dist/cloudflare/core/annotation-tools.js +230 -0
- package/dist/cloudflare/core/cloud-websocket-connector.js +93 -0
- package/dist/cloudflare/core/deep-component-tools.js +128 -0
- package/dist/cloudflare/core/design-code-tools.js +65 -7
- package/dist/cloudflare/core/enrichment/enrichment-service.js +108 -12
- package/dist/cloudflare/core/figjam-tools.js +485 -0
- package/dist/cloudflare/core/figma-api.js +7 -4
- package/dist/cloudflare/core/figma-desktop-connector.js +108 -0
- package/dist/cloudflare/core/figma-tools.js +445 -55
- package/dist/cloudflare/core/port-discovery.js +88 -0
- package/dist/cloudflare/core/resolve-package-root.js +11 -0
- package/dist/cloudflare/core/slides-tools.js +607 -0
- package/dist/cloudflare/core/websocket-connector.js +93 -0
- package/dist/cloudflare/core/websocket-server.js +18 -9
- package/dist/cloudflare/index.js +164 -41
- package/dist/core/annotation-tools.d.ts +14 -0
- package/dist/core/annotation-tools.d.ts.map +1 -0
- package/dist/core/annotation-tools.js +231 -0
- package/dist/core/annotation-tools.js.map +1 -0
- package/dist/core/deep-component-tools.d.ts +14 -0
- package/dist/core/deep-component-tools.d.ts.map +1 -0
- package/dist/core/deep-component-tools.js +129 -0
- package/dist/core/deep-component-tools.js.map +1 -0
- package/dist/core/design-code-tools.d.ts.map +1 -1
- package/dist/core/design-code-tools.js +65 -7
- package/dist/core/design-code-tools.js.map +1 -1
- package/dist/core/enrichment/enrichment-service.d.ts.map +1 -1
- package/dist/core/enrichment/enrichment-service.js +108 -12
- package/dist/core/enrichment/enrichment-service.js.map +1 -1
- package/dist/core/figma-api.d.ts +1 -1
- package/dist/core/figma-api.d.ts.map +1 -1
- package/dist/core/figma-api.js +7 -4
- package/dist/core/figma-api.js.map +1 -1
- package/dist/core/figma-connector.d.ts +5 -0
- package/dist/core/figma-connector.d.ts.map +1 -1
- package/dist/core/figma-desktop-connector.d.ts +20 -0
- package/dist/core/figma-desktop-connector.d.ts.map +1 -1
- package/dist/core/figma-desktop-connector.js +83 -0
- package/dist/core/figma-desktop-connector.js.map +1 -1
- package/dist/core/figma-tools.d.ts.map +1 -1
- package/dist/core/figma-tools.js +355 -26
- package/dist/core/figma-tools.js.map +1 -1
- package/dist/core/port-discovery.d.ts +21 -0
- package/dist/core/port-discovery.d.ts.map +1 -1
- package/dist/core/port-discovery.js +88 -0
- package/dist/core/port-discovery.js.map +1 -1
- package/dist/core/resolve-package-root.d.ts +2 -0
- package/dist/core/resolve-package-root.d.ts.map +1 -0
- package/dist/core/resolve-package-root.js +12 -0
- package/dist/core/resolve-package-root.js.map +1 -0
- package/dist/core/types/design-code.d.ts +1 -0
- package/dist/core/types/design-code.d.ts.map +1 -1
- package/dist/core/websocket-connector.d.ts +5 -0
- package/dist/core/websocket-connector.d.ts.map +1 -1
- package/dist/core/websocket-connector.js +18 -0
- package/dist/core/websocket-connector.js.map +1 -1
- package/dist/core/websocket-server.d.ts.map +1 -1
- package/dist/core/websocket-server.js +7 -9
- package/dist/core/websocket-server.js.map +1 -1
- package/dist/local.d.ts +6 -0
- package/dist/local.d.ts.map +1 -1
- package/dist/local.js +58 -1
- package/dist/local.js.map +1 -1
- package/figma-desktop-bridge/code.js +906 -4
- package/figma-desktop-bridge/ui-full.html +80 -0
- package/figma-desktop-bridge/ui.html +82 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
> **🔒 Security Reviewed Fork:** This fork (`@mp3wizard/figma-console-mcp`) has passed a full security review following OWASP Top 10 and CWE standards, including automated scanning (Semgrep, Trivy, TruffleHog) and manual vulnerability analysis. Review reports are available in the [`Security review report/`](Security%20review%20report/) folder.
|
|
13
13
|
|
|
14
|
-
> **🆕
|
|
14
|
+
> **🆕 High-Fidelity Design-to-Code:** Deep component trees (depth 4), resolved design tokens, interaction state machines with CSS mappings, and codebase-aware component scanning. AI gets everything a senior engineer needs — tokens, sizing, states, annotations, and a cross-reference of what already exists in your codebase. [See what's new →](docs/figma-mcp-vs-figma-console-mcp.md)
|
|
15
15
|
|
|
16
16
|
## What is this?
|
|
17
17
|
|
|
@@ -54,9 +54,9 @@ Figma Console MCP connects AI assistants (like Claude) to Figma, enabling:
|
|
|
54
54
|
| Real-time monitoring (console, selection) | ✅ | ❌ | ❌ |
|
|
55
55
|
| Desktop Bridge plugin | ✅ | ✅ | ❌ |
|
|
56
56
|
| Requires Node.js | Yes | **No** | No |
|
|
57
|
-
| **Total tools available** | **
|
|
57
|
+
| **Total tools available** | **89+** | **43** | **22** |
|
|
58
58
|
|
|
59
|
-
> **Bottom line:** Remote SSE is **read-only** with ~38% of the tools. **Cloud Mode** unlocks write access from web AI clients without Node.js. NPX/Local Git gives the full
|
|
59
|
+
> **Bottom line:** Remote SSE is **read-only** with ~38% of the tools. **Cloud Mode** unlocks write access from web AI clients without Node.js. NPX/Local Git gives the full 89+ tools with real-time monitoring.
|
|
60
60
|
|
|
61
61
|
---
|
|
62
62
|
|
|
@@ -64,7 +64,7 @@ Figma Console MCP connects AI assistants (like Claude) to Figma, enabling:
|
|
|
64
64
|
|
|
65
65
|
**Best for:** Designers who want full AI-assisted design capabilities.
|
|
66
66
|
|
|
67
|
-
**What you get:** All
|
|
67
|
+
**What you get:** All 89+ tools including design creation, variable management, and component instantiation.
|
|
68
68
|
|
|
69
69
|
#### Prerequisites
|
|
70
70
|
|
|
@@ -77,7 +77,8 @@ Figma Console MCP connects AI assistants (like Claude) to Figma, enabling:
|
|
|
77
77
|
1. Go to [Manage personal access tokens](https://help.figma.com/hc/en-us/articles/8085703771159-Manage-personal-access-tokens) in Figma Help
|
|
78
78
|
2. Follow the steps to **create a new personal access token**
|
|
79
79
|
3. Enter description: `Figma Console MCP`
|
|
80
|
-
4.
|
|
80
|
+
4. Set scopes: **File content** (Read), **Variables** (Read), **Comments** (Read and write)
|
|
81
|
+
5. **Copy the token** — you won't see it again! (starts with `figd_`)
|
|
81
82
|
|
|
82
83
|
#### Step 2: Configure Your MCP Client
|
|
83
84
|
|
|
@@ -158,7 +159,7 @@ Create a simple frame with a blue background
|
|
|
158
159
|
|
|
159
160
|
**Best for:** Developers who want to modify source code or contribute to the project.
|
|
160
161
|
|
|
161
|
-
**What you get:** Same
|
|
162
|
+
**What you get:** Same 89+ tools as NPX, plus full source code access.
|
|
162
163
|
|
|
163
164
|
#### Quick Setup
|
|
164
165
|
|
|
@@ -247,7 +248,7 @@ Ready for design creation? Follow the [NPX Setup](#-npx-setup-recommended) guide
|
|
|
247
248
|
|
|
248
249
|
**Best for:** Using Claude.ai, v0, Replit, or Lovable to create and modify Figma designs — no Node.js required.
|
|
249
250
|
|
|
250
|
-
**What you get:**
|
|
251
|
+
**What you get:** 79 tools including full write access — design creation, variable management, component instantiation, and all REST API tools. Only real-time monitoring (console logs, selection tracking, document changes) requires Local Mode.
|
|
251
252
|
|
|
252
253
|
#### Prerequisites
|
|
253
254
|
|
|
@@ -304,7 +305,7 @@ AI Client → Cloud MCP Server → Durable Object Relay → Desktop Bridge Plugi
|
|
|
304
305
|
| Feature | NPX (Recommended) | Cloud Mode | Local Git | Remote SSE |
|
|
305
306
|
|---------|-------------------|------------|-----------|------------|
|
|
306
307
|
| **Setup time** | ~10 minutes | ~5 minutes | ~15 minutes | ~2 minutes |
|
|
307
|
-
| **Total tools** | **
|
|
308
|
+
| **Total tools** | **89+** | **43** | **89+** | **22** (read-only) |
|
|
308
309
|
| **Design creation** | ✅ | ✅ | ✅ | ❌ |
|
|
309
310
|
| **Variable management** | ✅ | ✅ | ✅ | ❌ |
|
|
310
311
|
| **Component instantiation** | ✅ | ✅ | ✅ | ❌ |
|
|
@@ -319,7 +320,7 @@ AI Client → Cloud MCP Server → Durable Object Relay → Desktop Bridge Plugi
|
|
|
319
320
|
| **Automatic updates** | ✅ (`@latest`) | ✅ | Manual (`git pull`) | ✅ |
|
|
320
321
|
| **Source code access** | ❌ | ❌ | ✅ | ❌ |
|
|
321
322
|
|
|
322
|
-
> **Key insight:** Remote SSE is read-only. Cloud Mode adds write access for web AI clients without Node.js. NPX/Local Git give the full
|
|
323
|
+
> **Key insight:** Remote SSE is read-only. Cloud Mode adds write access for web AI clients without Node.js. NPX/Local Git give the full 89+ tools.
|
|
323
324
|
|
|
324
325
|
**📖 [Complete Feature Comparison](docs/mode-comparison.md)**
|
|
325
326
|
|
|
@@ -365,7 +366,7 @@ When you first use design system tools:
|
|
|
365
366
|
### Local Mode - Personal Access Token (Manual)
|
|
366
367
|
|
|
367
368
|
1. Visit https://help.figma.com/hc/en-us/articles/8085703771159-Manage-personal-access-tokens
|
|
368
|
-
2. Generate token
|
|
369
|
+
2. Generate token with scopes: **File content** (Read), **Variables** (Read), **Comments** (Read and write)
|
|
369
370
|
3. Add to MCP config as `FIGMA_ACCESS_TOKEN` environment variable
|
|
370
371
|
|
|
371
372
|
---
|
|
@@ -651,7 +652,7 @@ The **Figma Desktop Bridge** plugin is the recommended way to connect Figma to t
|
|
|
651
652
|
- The MCP server communicates via **WebSocket** through the Desktop Bridge plugin
|
|
652
653
|
- The server tries port 9223 first, then automatically falls back through ports 9224–9232 if needed
|
|
653
654
|
- The plugin scans all ports in the range and connects to every active server it finds
|
|
654
|
-
- All
|
|
655
|
+
- All 89+ tools work through the WebSocket transport
|
|
655
656
|
|
|
656
657
|
**Multiple files:** The WebSocket server supports multiple simultaneous plugin connections — one per open Figma file. Each connection is tracked by file key with independent state (selection, document changes, console logs).
|
|
657
658
|
|
|
@@ -788,7 +789,7 @@ The architecture supports adding new apps with minimal boilerplate — each app
|
|
|
788
789
|
|
|
789
790
|
## 🛤️ Roadmap
|
|
790
791
|
|
|
791
|
-
**Current Status:** v1.17.0 (Stable) - Production-ready with FigJam + Slides support, Cloud Write Relay, Design System Kit, WebSocket-only connectivity, smart multi-file tracking,
|
|
792
|
+
**Current Status:** v1.17.0 (Stable) - Production-ready with FigJam + Slides support, Cloud Write Relay, Design System Kit, WebSocket-only connectivity, smart multi-file tracking, 89+ tools, Comments API, and MCP Apps
|
|
792
793
|
|
|
793
794
|
**Recent Releases:**
|
|
794
795
|
- [x] **v1.17.0** - Figma Slides Support: 15 new tools for managing presentations — slides, transitions, content, reordering, and navigation. Inspired by Toni Haidamous (PR #11).
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Figma Annotations MCP Tools
|
|
3
|
+
* Tools for reading, writing, and managing design annotations on Figma nodes.
|
|
4
|
+
* Annotations are a Plugin API feature — requires Desktop Bridge plugin connection.
|
|
5
|
+
*
|
|
6
|
+
* Annotations are distinct from comments: they are node-level design specs that
|
|
7
|
+
* can pin specific properties (fills, width, typography, etc.) and support
|
|
8
|
+
* markdown-formatted labels. Designers use them to communicate animation timings,
|
|
9
|
+
* accessibility requirements, interaction specs, and other implementation details
|
|
10
|
+
* that don't fit in the description field.
|
|
11
|
+
*/
|
|
12
|
+
import { z } from "zod";
|
|
13
|
+
import { createChildLogger } from "./logger.js";
|
|
14
|
+
const logger = createChildLogger({ component: "annotation-tools" });
|
|
15
|
+
// Valid AnnotationPropertyType values from the Figma Plugin API
|
|
16
|
+
// Sourced from @figma/plugin-typings — keep in sync with AnnotationPropertyType union
|
|
17
|
+
// Reference: https://developers.figma.com/docs/plugins/api/AnnotationProperty
|
|
18
|
+
const ANNOTATION_PROPERTY_TYPES = [
|
|
19
|
+
"width",
|
|
20
|
+
"height",
|
|
21
|
+
"maxWidth",
|
|
22
|
+
"minWidth",
|
|
23
|
+
"maxHeight",
|
|
24
|
+
"minHeight",
|
|
25
|
+
"fills",
|
|
26
|
+
"strokes",
|
|
27
|
+
"effects",
|
|
28
|
+
"strokeWeight",
|
|
29
|
+
"cornerRadius",
|
|
30
|
+
"textStyleId",
|
|
31
|
+
"textAlignHorizontal",
|
|
32
|
+
"fontFamily",
|
|
33
|
+
"fontStyle",
|
|
34
|
+
"fontSize",
|
|
35
|
+
"fontWeight",
|
|
36
|
+
"lineHeight",
|
|
37
|
+
"letterSpacing",
|
|
38
|
+
"itemSpacing",
|
|
39
|
+
"padding",
|
|
40
|
+
"layoutMode",
|
|
41
|
+
"alignItems",
|
|
42
|
+
"opacity",
|
|
43
|
+
"mainComponent",
|
|
44
|
+
"gridRowGap",
|
|
45
|
+
"gridColumnGap",
|
|
46
|
+
"gridRowCount",
|
|
47
|
+
"gridColumnCount",
|
|
48
|
+
"gridRowAnchorIndex",
|
|
49
|
+
"gridColumnAnchorIndex",
|
|
50
|
+
"gridRowSpan",
|
|
51
|
+
"gridColumnSpan",
|
|
52
|
+
];
|
|
53
|
+
// Zod schema for annotation property
|
|
54
|
+
const annotationPropertySchema = z.object({
|
|
55
|
+
type: z
|
|
56
|
+
.enum(ANNOTATION_PROPERTY_TYPES)
|
|
57
|
+
.describe("Design property to pin (e.g., 'fills', 'width', 'fontSize')"),
|
|
58
|
+
});
|
|
59
|
+
// Zod schema for a single annotation
|
|
60
|
+
const annotationSchema = z.object({
|
|
61
|
+
label: z
|
|
62
|
+
.string()
|
|
63
|
+
.optional()
|
|
64
|
+
.describe("Plain text annotation label"),
|
|
65
|
+
labelMarkdown: z
|
|
66
|
+
.string()
|
|
67
|
+
.optional()
|
|
68
|
+
.describe("Rich text annotation label with markdown formatting. Supports bold, italic, links, lists, code, and headers."),
|
|
69
|
+
properties: z
|
|
70
|
+
.array(annotationPropertySchema)
|
|
71
|
+
.optional()
|
|
72
|
+
.describe("Design properties to pin to this annotation (e.g., fills, width, fontSize)"),
|
|
73
|
+
categoryId: z
|
|
74
|
+
.string()
|
|
75
|
+
.optional()
|
|
76
|
+
.describe("Annotation category ID. Use figma_get_annotation_categories to list available categories."),
|
|
77
|
+
});
|
|
78
|
+
// ============================================================================
|
|
79
|
+
// Tool Registration
|
|
80
|
+
// ============================================================================
|
|
81
|
+
export function registerAnnotationTools(server, getDesktopConnector) {
|
|
82
|
+
// -----------------------------------------------------------------------
|
|
83
|
+
// Tool: figma_get_annotations
|
|
84
|
+
// -----------------------------------------------------------------------
|
|
85
|
+
server.tool("figma_get_annotations", "Read annotations from a Figma node. Annotations are designer-authored specs attached to nodes — they can include notes (plain text or markdown), pinned design properties (fills, width, fontSize, etc.), and category labels. Use this to discover animation timings, interaction specs, accessibility requirements, and other implementation details that designers annotate directly on the design. Set include_children=true to get annotations from child nodes too (useful for full component documentation). Requires Desktop Bridge plugin.", {
|
|
86
|
+
nodeId: z
|
|
87
|
+
.string()
|
|
88
|
+
.describe("Node ID to read annotations from (e.g., '695:313')"),
|
|
89
|
+
include_children: z.preprocess((v) => (typeof v === "string" ? v === "true" : v), z.boolean().optional().default(false)).describe("Also read annotations from child nodes. Useful for getting all annotations within a component tree."),
|
|
90
|
+
depth: z.preprocess((v) => (typeof v === "string" ? Number(v) : v), z.number().optional().default(1)).describe("How many levels deep to traverse when include_children is true (default: 1, max recommended: 5)"),
|
|
91
|
+
}, async ({ nodeId, include_children = false, depth = 1 }) => {
|
|
92
|
+
try {
|
|
93
|
+
logger.info({ nodeId, include_children, depth }, "Getting annotations");
|
|
94
|
+
const connector = await getDesktopConnector();
|
|
95
|
+
const result = await connector.getAnnotations(nodeId, include_children, Math.min(depth, 10));
|
|
96
|
+
if (!result || (result.success === false)) {
|
|
97
|
+
throw new Error(result?.error || "Failed to get annotations");
|
|
98
|
+
}
|
|
99
|
+
// The result may come back as { success, data } (WebSocket) or directly as data
|
|
100
|
+
const data = result.data || result;
|
|
101
|
+
return {
|
|
102
|
+
content: [
|
|
103
|
+
{
|
|
104
|
+
type: "text",
|
|
105
|
+
text: JSON.stringify(data),
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
112
|
+
logger.error({ error }, "Failed to get annotations");
|
|
113
|
+
return {
|
|
114
|
+
content: [
|
|
115
|
+
{
|
|
116
|
+
type: "text",
|
|
117
|
+
text: JSON.stringify({
|
|
118
|
+
error: "get_annotations_failed",
|
|
119
|
+
message: `Cannot get annotations. ${message}`,
|
|
120
|
+
hint: "Annotations require the Desktop Bridge plugin to be running in Figma.",
|
|
121
|
+
}),
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
isError: true,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
// -----------------------------------------------------------------------
|
|
129
|
+
// Tool: figma_set_annotations
|
|
130
|
+
// -----------------------------------------------------------------------
|
|
131
|
+
server.tool("figma_set_annotations", "Write or clear annotations on a Figma node. Annotations communicate design specs to developers — use them to document animation timings, easing curves, interaction behaviors, accessibility requirements, and implementation notes. Supports plain text labels, rich markdown labels, pinned design properties, and annotation categories. Pass an empty array to clear all annotations. Use mode='append' to add to existing annotations, or mode='replace' (default) to overwrite. Requires Desktop Bridge plugin. This operation is undoable in Figma (Cmd+Z).", {
|
|
132
|
+
nodeId: z
|
|
133
|
+
.string()
|
|
134
|
+
.describe("Node ID to write annotations to (e.g., '695:313')"),
|
|
135
|
+
annotations: z.preprocess((v) => {
|
|
136
|
+
if (typeof v === "string") {
|
|
137
|
+
try {
|
|
138
|
+
return JSON.parse(v);
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
return v;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return v;
|
|
145
|
+
}, z.array(annotationSchema)).describe("Array of annotations to set. Each annotation can have a label (plain or markdown), pinned properties, and a category. Pass an empty array [] to clear all annotations."),
|
|
146
|
+
mode: z
|
|
147
|
+
.enum(["replace", "append"])
|
|
148
|
+
.optional()
|
|
149
|
+
.default("replace")
|
|
150
|
+
.describe("'replace' (default) overwrites all existing annotations. 'append' adds new annotations while keeping existing ones."),
|
|
151
|
+
}, async ({ nodeId, annotations, mode = "replace" }) => {
|
|
152
|
+
try {
|
|
153
|
+
logger.info({ nodeId, count: annotations.length, mode }, "Setting annotations");
|
|
154
|
+
const connector = await getDesktopConnector();
|
|
155
|
+
const result = await connector.setAnnotations(nodeId, annotations, mode);
|
|
156
|
+
if (!result || (result.success === false)) {
|
|
157
|
+
throw new Error(result?.error || "Failed to set annotations");
|
|
158
|
+
}
|
|
159
|
+
const data = result.data || result;
|
|
160
|
+
return {
|
|
161
|
+
content: [
|
|
162
|
+
{
|
|
163
|
+
type: "text",
|
|
164
|
+
text: JSON.stringify({
|
|
165
|
+
success: true,
|
|
166
|
+
...data,
|
|
167
|
+
note: "Annotations set successfully. This operation is undoable in Figma (Cmd+Z).",
|
|
168
|
+
}),
|
|
169
|
+
},
|
|
170
|
+
],
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
175
|
+
logger.error({ error }, "Failed to set annotations");
|
|
176
|
+
return {
|
|
177
|
+
content: [
|
|
178
|
+
{
|
|
179
|
+
type: "text",
|
|
180
|
+
text: JSON.stringify({
|
|
181
|
+
error: "set_annotations_failed",
|
|
182
|
+
message: `Cannot set annotations. ${message}`,
|
|
183
|
+
hint: "Annotations require the Desktop Bridge plugin to be running in Figma.",
|
|
184
|
+
}),
|
|
185
|
+
},
|
|
186
|
+
],
|
|
187
|
+
isError: true,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
// -----------------------------------------------------------------------
|
|
192
|
+
// Tool: figma_get_annotation_categories
|
|
193
|
+
// -----------------------------------------------------------------------
|
|
194
|
+
server.tool("figma_get_annotation_categories", "List available annotation categories in the current Figma file. Categories group annotations by purpose (e.g., interactions, accessibility, development notes). Use the returned category IDs when creating annotations with figma_set_annotations. Requires Desktop Bridge plugin.", {}, async () => {
|
|
195
|
+
try {
|
|
196
|
+
logger.info("Getting annotation categories");
|
|
197
|
+
const connector = await getDesktopConnector();
|
|
198
|
+
const result = await connector.getAnnotationCategories();
|
|
199
|
+
if (!result || (result.success === false)) {
|
|
200
|
+
throw new Error(result?.error || "Failed to get annotation categories");
|
|
201
|
+
}
|
|
202
|
+
const data = result.data || result;
|
|
203
|
+
return {
|
|
204
|
+
content: [
|
|
205
|
+
{
|
|
206
|
+
type: "text",
|
|
207
|
+
text: JSON.stringify(data),
|
|
208
|
+
},
|
|
209
|
+
],
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
214
|
+
logger.error({ error }, "Failed to get annotation categories");
|
|
215
|
+
return {
|
|
216
|
+
content: [
|
|
217
|
+
{
|
|
218
|
+
type: "text",
|
|
219
|
+
text: JSON.stringify({
|
|
220
|
+
error: "get_annotation_categories_failed",
|
|
221
|
+
message: `Cannot get annotation categories. ${message}`,
|
|
222
|
+
hint: "Annotation categories require the Desktop Bridge plugin to be running in Figma.",
|
|
223
|
+
}),
|
|
224
|
+
},
|
|
225
|
+
],
|
|
226
|
+
isError: true,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
}
|
|
@@ -130,6 +130,24 @@ export class CloudWebSocketConnector {
|
|
|
130
130
|
async setNodeDescription(nodeId, description, descriptionMarkdown) {
|
|
131
131
|
return this.sendCommand('SET_NODE_DESCRIPTION', { nodeId, description, descriptionMarkdown });
|
|
132
132
|
}
|
|
133
|
+
// ============================================================================
|
|
134
|
+
// Annotation operations
|
|
135
|
+
// ============================================================================
|
|
136
|
+
async getAnnotations(nodeId, includeChildren, depth) {
|
|
137
|
+
return this.sendCommand('GET_ANNOTATIONS', { nodeId, includeChildren, depth }, 10000);
|
|
138
|
+
}
|
|
139
|
+
async setAnnotations(nodeId, annotations, mode) {
|
|
140
|
+
return this.sendCommand('SET_ANNOTATIONS', { nodeId, annotations, mode: mode || 'replace' });
|
|
141
|
+
}
|
|
142
|
+
async getAnnotationCategories() {
|
|
143
|
+
return this.sendCommand('GET_ANNOTATION_CATEGORIES', {}, 5000);
|
|
144
|
+
}
|
|
145
|
+
async deepGetComponent(nodeId, depth) {
|
|
146
|
+
return this.sendCommand('DEEP_GET_COMPONENT', { nodeId, depth: depth || 10 }, 30000);
|
|
147
|
+
}
|
|
148
|
+
async analyzeComponentSet(nodeId) {
|
|
149
|
+
return this.sendCommand('ANALYZE_COMPONENT_SET', { nodeId }, 30000);
|
|
150
|
+
}
|
|
133
151
|
async addComponentProperty(nodeId, propertyName, type, defaultValue, options) {
|
|
134
152
|
const params = { nodeId, propertyName, propertyType: type, defaultValue };
|
|
135
153
|
if (options?.preferredValues)
|
|
@@ -244,6 +262,81 @@ export class CloudWebSocketConnector {
|
|
|
244
262
|
return this.sendCommand('LINT_DESIGN', params, 120000);
|
|
245
263
|
}
|
|
246
264
|
// ============================================================================
|
|
265
|
+
// FigJam operations
|
|
266
|
+
// ============================================================================
|
|
267
|
+
async createSticky(params) {
|
|
268
|
+
return this.sendCommand('CREATE_STICKY', params);
|
|
269
|
+
}
|
|
270
|
+
async createStickies(params) {
|
|
271
|
+
return this.sendCommand('CREATE_STICKIES', params, 30000);
|
|
272
|
+
}
|
|
273
|
+
async createConnector(params) {
|
|
274
|
+
return this.sendCommand('CREATE_CONNECTOR', params);
|
|
275
|
+
}
|
|
276
|
+
async createShapeWithText(params) {
|
|
277
|
+
return this.sendCommand('CREATE_SHAPE_WITH_TEXT', params);
|
|
278
|
+
}
|
|
279
|
+
async createTable(params) {
|
|
280
|
+
return this.sendCommand('CREATE_TABLE', params, 30000);
|
|
281
|
+
}
|
|
282
|
+
async createCodeBlock(params) {
|
|
283
|
+
return this.sendCommand('CREATE_CODE_BLOCK', params);
|
|
284
|
+
}
|
|
285
|
+
async getBoardContents(params) {
|
|
286
|
+
return this.sendCommand('GET_BOARD_CONTENTS', params, 30000);
|
|
287
|
+
}
|
|
288
|
+
async getConnections() {
|
|
289
|
+
return this.sendCommand('GET_CONNECTIONS', {}, 15000);
|
|
290
|
+
}
|
|
291
|
+
// ============================================================================
|
|
292
|
+
// Slides operations
|
|
293
|
+
// ============================================================================
|
|
294
|
+
async listSlides() {
|
|
295
|
+
return this.sendCommand('LIST_SLIDES', {}, 10000);
|
|
296
|
+
}
|
|
297
|
+
async getSlideContent(params) {
|
|
298
|
+
return this.sendCommand('GET_SLIDE_CONTENT', params, 10000);
|
|
299
|
+
}
|
|
300
|
+
async createSlide(params) {
|
|
301
|
+
return this.sendCommand('CREATE_SLIDE', params, 10000);
|
|
302
|
+
}
|
|
303
|
+
async deleteSlide(params) {
|
|
304
|
+
return this.sendCommand('DELETE_SLIDE', params, 5000);
|
|
305
|
+
}
|
|
306
|
+
async duplicateSlide(params) {
|
|
307
|
+
return this.sendCommand('DUPLICATE_SLIDE', params, 5000);
|
|
308
|
+
}
|
|
309
|
+
async getSlideGrid() {
|
|
310
|
+
return this.sendCommand('GET_SLIDE_GRID', {}, 10000);
|
|
311
|
+
}
|
|
312
|
+
async reorderSlides(params) {
|
|
313
|
+
return this.sendCommand('REORDER_SLIDES', params, 15000);
|
|
314
|
+
}
|
|
315
|
+
async setSlideTransition(params) {
|
|
316
|
+
return this.sendCommand('SET_SLIDE_TRANSITION', params, 5000);
|
|
317
|
+
}
|
|
318
|
+
async getSlideTransition(params) {
|
|
319
|
+
return this.sendCommand('GET_SLIDE_TRANSITION', params, 5000);
|
|
320
|
+
}
|
|
321
|
+
async setSlidesViewMode(params) {
|
|
322
|
+
return this.sendCommand('SET_SLIDES_VIEW_MODE', params, 5000);
|
|
323
|
+
}
|
|
324
|
+
async getFocusedSlide() {
|
|
325
|
+
return this.sendCommand('GET_FOCUSED_SLIDE', {}, 5000);
|
|
326
|
+
}
|
|
327
|
+
async focusSlide(params) {
|
|
328
|
+
return this.sendCommand('FOCUS_SLIDE', params, 5000);
|
|
329
|
+
}
|
|
330
|
+
async skipSlide(params) {
|
|
331
|
+
return this.sendCommand('SKIP_SLIDE', params, 5000);
|
|
332
|
+
}
|
|
333
|
+
async addTextToSlide(params) {
|
|
334
|
+
return this.sendCommand('ADD_TEXT_TO_SLIDE', params, 10000);
|
|
335
|
+
}
|
|
336
|
+
async addShapeToSlide(params) {
|
|
337
|
+
return this.sendCommand('ADD_SHAPE_TO_SLIDE', params, 5000);
|
|
338
|
+
}
|
|
339
|
+
// ============================================================================
|
|
247
340
|
// Cache management (no-op for cloud relay)
|
|
248
341
|
// ============================================================================
|
|
249
342
|
clearFrameCache() {
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deep Component Extraction MCP Tool
|
|
3
|
+
*
|
|
4
|
+
* Provides unlimited-depth component tree extraction via the Desktop Bridge
|
|
5
|
+
* Plugin API. Returns full visual properties, resolved design token names,
|
|
6
|
+
* instance references (mainComponent), prototype reactions, and annotations
|
|
7
|
+
* at every level of the tree.
|
|
8
|
+
*
|
|
9
|
+
* This complements figma_get_component_for_development (REST API, depth 4)
|
|
10
|
+
* with deeper, richer data when the Desktop Bridge plugin is connected.
|
|
11
|
+
*/
|
|
12
|
+
import { z } from "zod";
|
|
13
|
+
import { createChildLogger } from "./logger.js";
|
|
14
|
+
const logger = createChildLogger({ component: "deep-component-tools" });
|
|
15
|
+
export function registerDeepComponentTools(server, getDesktopConnector) {
|
|
16
|
+
server.tool("figma_get_component_for_development_deep", "Get a deeply nested component tree with full visual properties, resolved design token names, instance references, prototype interactions, and annotations at every level. Uses the Desktop Bridge Plugin API for unlimited depth traversal — essential for complex components like data tables, nested menus, date pickers, and compound form fields where the standard depth-4 REST API tool misses deeper structure. Returns boundVariables resolved to actual token names (not just IDs), mainComponent references for INSTANCE nodes, and reactions for interaction states. Requires Desktop Bridge plugin. For simpler components (depth ≤ 4), use figma_get_component_for_development instead.", {
|
|
17
|
+
nodeId: z
|
|
18
|
+
.string()
|
|
19
|
+
.describe("Component node ID to extract (e.g., '695:313')"),
|
|
20
|
+
depth: z.preprocess((v) => (typeof v === "string" ? Number(v) : v), z.number().optional().default(10)).describe("Maximum tree depth to traverse (default: 10, max: 20). Use higher values for deeply nested components."),
|
|
21
|
+
}, async ({ nodeId, depth = 10 }) => {
|
|
22
|
+
try {
|
|
23
|
+
const clampedDepth = Math.min(Math.max(depth, 1), 20);
|
|
24
|
+
logger.info({ nodeId, depth: clampedDepth }, "Deep component extraction");
|
|
25
|
+
const connector = await getDesktopConnector();
|
|
26
|
+
const result = await connector.deepGetComponent(nodeId, clampedDepth);
|
|
27
|
+
if (!result || (result.success === false)) {
|
|
28
|
+
throw new Error(result?.error || "Failed to extract component");
|
|
29
|
+
}
|
|
30
|
+
const data = result.data || result;
|
|
31
|
+
// Measure response size and warn if large
|
|
32
|
+
const responseJson = JSON.stringify(data);
|
|
33
|
+
const sizeKB = Math.round(responseJson.length / 1024);
|
|
34
|
+
const response = {
|
|
35
|
+
nodeId,
|
|
36
|
+
component: data,
|
|
37
|
+
metadata: {
|
|
38
|
+
purpose: "deep_component_development",
|
|
39
|
+
treeDepth: clampedDepth,
|
|
40
|
+
responseSizeKB: sizeKB,
|
|
41
|
+
variablesResolved: data._variableMapSize || 0,
|
|
42
|
+
note: [
|
|
43
|
+
`Deep component tree extracted via Plugin API (depth ${clampedDepth}).`,
|
|
44
|
+
"boundVariables are resolved to token names, collections, and codeSyntax.",
|
|
45
|
+
"INSTANCE nodes include mainComponent references (key, name, component set).",
|
|
46
|
+
"Use this data to generate production-quality, token-aware, accessible code.",
|
|
47
|
+
sizeKB > 200 ? "Response is large — consider targeting a specific child node for deeper analysis." : null,
|
|
48
|
+
].filter(Boolean).join(" "),
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
return {
|
|
52
|
+
content: [
|
|
53
|
+
{
|
|
54
|
+
type: "text",
|
|
55
|
+
text: JSON.stringify(response),
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
62
|
+
logger.error({ error }, "Deep component extraction failed");
|
|
63
|
+
return {
|
|
64
|
+
content: [
|
|
65
|
+
{
|
|
66
|
+
type: "text",
|
|
67
|
+
text: JSON.stringify({
|
|
68
|
+
error: "deep_component_failed",
|
|
69
|
+
message: `Cannot extract deep component. ${message}`,
|
|
70
|
+
hint: "This tool requires the Desktop Bridge plugin to be running in Figma. For REST API fallback (depth 4), use figma_get_component_for_development.",
|
|
71
|
+
}),
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
isError: true,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
// -----------------------------------------------------------------------
|
|
79
|
+
// Tool: figma_analyze_component_set
|
|
80
|
+
// -----------------------------------------------------------------------
|
|
81
|
+
server.tool("figma_analyze_component_set", "Analyze a Figma COMPONENT_SET to extract variant state machine and cross-variant diffs for code generation. Returns: (1) variant axes (size, state) with all values, (2) CSS pseudo-class mappings for interaction states (hover→:hover, focus→:focus-visible, disabled→:disabled, error→[aria-invalid]), (3) visual diff from default state per variant (only changed properties — fill token, stroke token, stroke weight, text color, opacity, effects, visibility), (4) component property definitions mapped to code props (BOOLEAN→boolean, TEXT→string, INSTANCE_SWAP→slot/ReactNode). Use this on the parent COMPONENT_SET node, not individual variants. Requires Desktop Bridge plugin.", {
|
|
82
|
+
nodeId: z
|
|
83
|
+
.string()
|
|
84
|
+
.describe("COMPONENT_SET node ID (the parent of all variants, e.g., '214:274')"),
|
|
85
|
+
}, async ({ nodeId }) => {
|
|
86
|
+
try {
|
|
87
|
+
logger.info({ nodeId }, "Analyzing component set");
|
|
88
|
+
const connector = await getDesktopConnector();
|
|
89
|
+
const result = await connector.analyzeComponentSet(nodeId);
|
|
90
|
+
if (!result || (result.success === false)) {
|
|
91
|
+
throw new Error(result?.error || "Failed to analyze component set");
|
|
92
|
+
}
|
|
93
|
+
const data = result.data || result;
|
|
94
|
+
return {
|
|
95
|
+
content: [
|
|
96
|
+
{
|
|
97
|
+
type: "text",
|
|
98
|
+
text: JSON.stringify({
|
|
99
|
+
nodeId,
|
|
100
|
+
analysis: data,
|
|
101
|
+
metadata: {
|
|
102
|
+
purpose: "variant_state_machine",
|
|
103
|
+
note: "Use cssMapping to implement interaction states as CSS pseudo-classes/attributes. diffFromDefault shows only what changes per variant — apply as style overrides. componentProps maps to your component's TypeScript interface.",
|
|
104
|
+
},
|
|
105
|
+
}),
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
112
|
+
logger.error({ error }, "Component set analysis failed");
|
|
113
|
+
return {
|
|
114
|
+
content: [
|
|
115
|
+
{
|
|
116
|
+
type: "text",
|
|
117
|
+
text: JSON.stringify({
|
|
118
|
+
error: "analyze_component_set_failed",
|
|
119
|
+
message: `Cannot analyze component set. ${message}`,
|
|
120
|
+
hint: "This tool requires the Desktop Bridge plugin and a COMPONENT_SET node ID (not an individual variant). Use figma_search_components to find component sets.",
|
|
121
|
+
}),
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
isError: true,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
}
|