figma-console-mcp 1.34.0 β 1.35.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/README.md +18 -10
- package/dist/cloudflare/core/cloud-websocket-connector.js +17 -0
- package/dist/cloudflare/core/figma-tools.js +1 -1
- package/dist/cloudflare/core/slot-tools.js +334 -0
- package/dist/cloudflare/core/tokens-tools.js +1 -1
- package/dist/cloudflare/core/websocket-connector.js +17 -0
- package/dist/cloudflare/core/write-tools.js +40 -9
- package/dist/cloudflare/index.js +10 -7
- package/dist/core/design-system-manifest.d.ts +1 -1
- package/dist/core/design-system-manifest.d.ts.map +1 -1
- package/dist/core/figma-connector.d.ts +22 -0
- package/dist/core/figma-connector.d.ts.map +1 -1
- package/dist/core/figma-tools.js +1 -1
- package/dist/core/figma-tools.js.map +1 -1
- package/dist/core/slot-tools.d.ts +7 -0
- package/dist/core/slot-tools.d.ts.map +1 -0
- package/dist/core/slot-tools.js +335 -0
- package/dist/core/slot-tools.js.map +1 -0
- package/dist/core/tokens-tools.js +1 -1
- package/dist/core/websocket-connector.d.ts +22 -0
- package/dist/core/websocket-connector.d.ts.map +1 -1
- package/dist/core/websocket-connector.js +17 -0
- package/dist/core/websocket-connector.js.map +1 -1
- package/dist/core/write-tools.d.ts.map +1 -1
- package/dist/core/write-tools.js +40 -9
- package/dist/core/write-tools.js.map +1 -1
- package/dist/local.d.ts.map +1 -1
- package/dist/local.js +2 -0
- package/dist/local.js.map +1 -1
- package/figma-desktop-bridge/code.js +395 -8
- package/figma-desktop-bridge/ui.html +43 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
> **Your design system as an API.** Model Context Protocol server that bridges design and developmentβgiving AI assistants complete access to Figma for **extraction**, **creation**, **debugging**, and **bidirectional token sync**.
|
|
10
10
|
|
|
11
|
-
> **π
|
|
11
|
+
> **π Figma Slots Support (v1.35.0):** Create, inspect, populate, and reset Figma Slots (GA at Config 2026) programmatically β `figma_create_slot` adds slots to components (variants included), `figma_append_to_slot` populates instance slots with cloned or new content, plus `figma_get_slots`, `figma_reset_slot`, and `figma_add_slot_property`. Live-validated against the GA Plugin API; closes the most-requested issue [#29](https://github.com/southleft/figma-console-mcp/issues/29), based on community PR [#77](https://github.com/southleft/figma-console-mcp/pull/77). **Plugin re-import required** (`code.js` + `ui.html` changed). [See what's new β](CHANGELOG.md#1350---2026-07-09)
|
|
12
12
|
|
|
13
13
|
## What is this?
|
|
14
14
|
|
|
@@ -55,9 +55,9 @@ Figma Console MCP connects AI assistants (like Claude) to Figma, enabling:
|
|
|
55
55
|
| Real-time monitoring (console, selection) | β
| β | β |
|
|
56
56
|
| Desktop Bridge plugin | β
| β
| β |
|
|
57
57
|
| Requires Node.js | Yes | **No** | No |
|
|
58
|
-
| **Total tools available** | **
|
|
58
|
+
| **Total tools available** | **112** | **101** | **9** |
|
|
59
59
|
|
|
60
|
-
> **Bottom line:** Remote SSE is **read-only** with 9 tools. **Cloud Mode** unlocks write access (
|
|
60
|
+
> **Bottom line:** Remote SSE is **read-only** with 9 tools. **Cloud Mode** unlocks write access (101 tools) from web AI clients without Node.js. NPX/Local Git gives the full 112 tools with real-time monitoring.
|
|
61
61
|
|
|
62
62
|
---
|
|
63
63
|
|
|
@@ -65,7 +65,7 @@ Figma Console MCP connects AI assistants (like Claude) to Figma, enabling:
|
|
|
65
65
|
|
|
66
66
|
**Best for:** Designers who want full AI-assisted design capabilities.
|
|
67
67
|
|
|
68
|
-
**What you get:** All
|
|
68
|
+
**What you get:** All 112 tools including design creation, variable management, and component instantiation.
|
|
69
69
|
|
|
70
70
|
#### Prerequisites
|
|
71
71
|
|
|
@@ -162,7 +162,7 @@ Create a simple frame with a blue background
|
|
|
162
162
|
|
|
163
163
|
**Best for:** Developers who want to modify source code or contribute to the project.
|
|
164
164
|
|
|
165
|
-
**What you get:** Same
|
|
165
|
+
**What you get:** Same 112 tools as NPX, plus full source code access.
|
|
166
166
|
|
|
167
167
|
#### Quick Setup
|
|
168
168
|
|
|
@@ -251,7 +251,7 @@ Ready for design creation? Follow the [NPX Setup](#-npx-setup-recommended) guide
|
|
|
251
251
|
|
|
252
252
|
**Best for:** Using Claude.ai, v0, Replit, or Lovable to create and modify Figma designs β no Node.js required.
|
|
253
253
|
|
|
254
|
-
**What you get:**
|
|
254
|
+
**What you get:** 101 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.
|
|
255
255
|
|
|
256
256
|
#### Prerequisites
|
|
257
257
|
|
|
@@ -308,7 +308,7 @@ AI Client β Cloud MCP Server β Durable Object Relay β Desktop Bridge Plugi
|
|
|
308
308
|
| Feature | NPX (Recommended) | Cloud Mode | Local Git | Remote SSE |
|
|
309
309
|
|---------|-------------------|------------|-----------|------------|
|
|
310
310
|
| **Setup time** | ~10 minutes | ~5 minutes | ~15 minutes | ~2 minutes |
|
|
311
|
-
| **Total tools** | **
|
|
311
|
+
| **Total tools** | **112** | **101** | **112** | **9** (read-only) |
|
|
312
312
|
| **Design creation** | β
| β
| β
| β |
|
|
313
313
|
| **Variable management** | β
| β
| β
| β |
|
|
314
314
|
| **Component instantiation** | β
| β
| β
| β |
|
|
@@ -323,7 +323,7 @@ AI Client β Cloud MCP Server β Durable Object Relay β Desktop Bridge Plugi
|
|
|
323
323
|
| **Automatic updates** | β
(`@latest`) | β
| Manual (`git pull`) | β
|
|
|
324
324
|
| **Source code access** | β | β | β
| β |
|
|
325
325
|
|
|
326
|
-
> **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
|
|
326
|
+
> **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 112 tools.
|
|
327
327
|
|
|
328
328
|
**π [Complete Feature Comparison](docs/mode-comparison.md)**
|
|
329
329
|
|
|
@@ -431,6 +431,13 @@ When you first use design system tools:
|
|
|
431
431
|
- Supports markdown formatting for rich documentation
|
|
432
432
|
- Descriptions appear in Dev Mode for developers
|
|
433
433
|
|
|
434
|
+
### π§© Figma Slots (Local Mode + Cloud Mode)
|
|
435
|
+
- `figma_create_slot` - **Add a slot to a component** via the GA `createSlot()` API β the linked SLOT property is created automatically; works on standalone components and variants inside a component set
|
|
436
|
+
- `figma_get_slots` - List slots on a component, component set (aggregated across variants), or instance β ids, names, property keys, dimensions, and current children
|
|
437
|
+
- `figma_append_to_slot` - **Populate an instance's slot** by cloning an existing node or creating new content (`setProperties` rejects slot values by design β this is the population path)
|
|
438
|
+
- `figma_reset_slot` - Clear a slot's content on an instance
|
|
439
|
+
- `figma_add_slot_property` - Retrofit an existing frame as a slot via a manual SLOT property binding, with `description` and `preferredValues`
|
|
440
|
+
|
|
434
441
|
### π Design-Code Parity (All Modes)
|
|
435
442
|
- `figma_check_design_parity` - Compare Figma component specs against code implementation, producing a scored diff report with actionable fix items
|
|
436
443
|
- `figma_generate_component_doc` - Generate platform-agnostic markdown documentation by merging Figma design data with code-side info
|
|
@@ -673,7 +680,7 @@ The **Figma Desktop Bridge** plugin is the recommended way to connect Figma to t
|
|
|
673
680
|
- The MCP server communicates via **WebSocket** through the Desktop Bridge plugin
|
|
674
681
|
- The server tries port 9223 first, then automatically falls back through ports 9224β9232 if needed
|
|
675
682
|
- The plugin scans all ports in the range and connects to every active server it finds
|
|
676
|
-
- All
|
|
683
|
+
- All 112 tools work through the WebSocket transport
|
|
677
684
|
|
|
678
685
|
**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).
|
|
679
686
|
|
|
@@ -812,9 +819,10 @@ The architecture supports adding new apps with minimal boilerplate β each app
|
|
|
812
819
|
|
|
813
820
|
## π€οΈ Roadmap
|
|
814
821
|
|
|
815
|
-
**Current Status:** v1.
|
|
822
|
+
**Current Status:** v1.35.0 (Stable) - Production-ready. Latest: Figma Slots write support β create, inspect, populate, and reset Slots (GA at Config 2026) via 5 new tools (`figma_create_slot`, `figma_get_slots`, `figma_append_to_slot`, `figma_reset_slot`, `figma_add_slot_property`), live-validated against the GA Plugin API and based on community PR #77. On top of v1.34.0's Bidirectional Token Sync v2 + DTCG 2025.10 β `figma_import_tokens` applies the complete diff plan (creates missing collections/variables, applies renames, writes real `VARIABLE_ALIAS` references, and deletes only under explicit `replace`), `figma_export_tokens` speaks the DTCG 2025.10 dialect on request (legacy default byte-identical), variable scopes/codeSyntax round-trip via `$extensions`, `figma_setup_design_tokens` accepts alias values via DTCG brace references, and `figma_create_component_set` builds a full variant set from an axes matrix in one call. On top of the v1.33.x line: version-handshake fix (re-import banner only fires when plugin files actually changed), security dependency sweep, and the v1.33.0 connection UX overhaul (honest status pill derived from live connection state, `/health` auto-discovery with self-healing reconnect) + a 33-fix full-codebase audit (lossless DTCG multi-mode round-trips, cross-collection alias resolution, branch-URL correctness across REST tools, cache-poisoning and CSWSH fixes, bridge-first screenshots). Built on WCAG-accurate accessibility auditing (line height below 1.5Γ is no longer mis-flagged as a failure; readability hints decoupled from conformance checks and scoped to multi-line text; code-side WCAG 1.4.12 check), a self-healing Desktop Bridge connection (zombie-process reaper + auto-reconnect watchdog β fixes the recurring "not connected until restart" bug), native variable binding on fills/strokes + typography control in the write tools, shared-library inspection (key-based component resolution + library variable read/import without Enterprise plan), 10-format token export pipeline (DTCG, CSS, Tailwind v4, Tailwind v3, SCSS, TS module, JSON flat/nested, Style Dictionary v3, Tokens Studio), bidirectional Figmaβcode token sync, version history & time-series awareness, FigJam + Slides support, Cloud Write Relay, Design System Kit, WebSocket-only connectivity, smart multi-file tracking, **112 tools** (Local) / **101 tools** (Cloud) / **9 tools** (Remote read-only), Comments API, cross-MCP identity disambiguation, and MCP Apps.
|
|
816
823
|
|
|
817
824
|
**Recent Releases:**
|
|
825
|
+
- [x] **v1.35.0** - Figma Slots write support, closing [#29](https://github.com/southleft/figma-console-mcp/issues/29) (the tracker's most-requested feature) via community PR [#77](https://github.com/southleft/figma-console-mcp/pull/77) by @simonesalvucci, updated to the GA API. Five new tools: `figma_create_slot` (slot + auto-linked SLOT property; variants inside component sets supported β the beta restriction was lifted at GA), `figma_get_slots`, `figma_append_to_slot` (clone or create content into instance slots; snaps clones to the slot origin), `figma_reset_slot`, and `figma_add_slot_property` (retrofit an existing frame as a slot). Hardened by live validation: VARIANT `defaultValue` passthrough restored (Figma requires non-empty), destructive-path reorder in the append handler (content validated before `clearExisting` empties anything), relay whitelist fix for `slot` payloads. **Plugin re-import required** (`code.js` + `ui.html` changed).
|
|
818
826
|
- [x] **v1.34.0** - Bidirectional Token Sync v2 + DTCG 2025.10. `figma_import_tokens` now applies the *complete* diff plan: missing collections and variables are created (with modes, inferred/recorded types, and values set in dependency order β aliases in a second pass), token-path renames route to the update phase by round-trip variable ID (no more create+delete pairs that would permanently destroy the original under `replace`), reference values write real `{ type: "VARIABLE_ALIAS", id }` payloads via a four-tier resolver, and deletes are strictly gated behind `strategy: "replace"`. `figma_export_tokens` gains `dtcgDialect: "2025"` (object-form colors from full-precision floats, object dimensions) while the legacy default stays byte-identical; import accepts both dialects unconditionally with dialect-insensitive diff normalization. Variable `scopes` + `codeSyntax` round-trip through `$extensions["figma-console-mcp"]`. `figma_setup_design_tokens` accepts DTCG brace references (`"{color.blue.600}"`) that resolve to real aliases, including forward references. New tool `figma_create_component_set` builds a variant set from an axes matrix (or combines existing components) with `Prop=Value` naming, optional auto-arranged grid, and variant keys in the response β with count-scaled timeouts and rollback on failure. **Plugin re-import required** (`code.js` + `ui.html` changed β the component-set handler and relay). 183 tests across the token/write-tools suites.
|
|
819
827
|
- [x] **v1.33.2** - Version-handshake false-positive fix. The v1.33.0 handshake compared the plugin's reported version against the server's *package* version, so server-only releases (like the v1.33.1 dependency sweep) flagged every up-to-date plugin as stale and pushed the re-import banner for files that hadn't changed. The server now compares against the `PLUGIN_VERSION` embedded in the `figma-desktop-bridge/code.js` it ships β exactly what a re-import would install β and `PLUGIN_VERSION` itself now means "last release in which plugin files changed" (release tooling bumps it only when `figma-desktop-bridge/` actually changed since the last tag). `figma_get_status` gains `transport.websocket.bundledPluginVersion`; `figma_diagnose` blames the right version. No new tools, **no plugin re-import required** (one-time exception: if you re-imported at v1.33.1, the banner appears once more β clear it with one final re-import). 1245 tests passing (9 new).
|
|
820
828
|
- [x] **v1.33.1** - Security dependency sweep. All runtime and critical npm audit alerts resolved via in-range bumps (`ws` 8.21.0, `hono` 4.12.27, `undici` 7.28.0, `handlebars` 4.7.9 β the lone critical, dev-only β plus `lodash`, `path-to-regexp`, `basic-ftp`, `fast-uri`, `vite`). `wrangler` deliberately held at 4.72.0 because newer versions require Node β₯22; the only residual audit findings are inside wrangler/miniflare's dev-time toolchain, which never ships in the npm package or Worker bundle. Supersedes dependabot PRs #81/#82/#84. No code changes, no API changes, no plugin re-import. 1236 tests passing unchanged.
|
|
@@ -154,6 +154,8 @@ export class CloudWebSocketConnector {
|
|
|
154
154
|
const params = { nodeId, propertyName, propertyType: type, defaultValue };
|
|
155
155
|
if (options?.preferredValues)
|
|
156
156
|
params.preferredValues = options.preferredValues;
|
|
157
|
+
if (options?.description)
|
|
158
|
+
params.description = options.description;
|
|
157
159
|
return this.sendCommand('ADD_COMPONENT_PROPERTY', params);
|
|
158
160
|
}
|
|
159
161
|
async editComponentProperty(nodeId, propertyName, newValue) {
|
|
@@ -199,6 +201,21 @@ export class CloudWebSocketConnector {
|
|
|
199
201
|
return this.sendCommand('CREATE_COMPONENT_SET', params, timeout);
|
|
200
202
|
}
|
|
201
203
|
// ============================================================================
|
|
204
|
+
// Slot operations
|
|
205
|
+
// ============================================================================
|
|
206
|
+
async createSlot(nodeId, options) {
|
|
207
|
+
return this.sendCommand('CREATE_SLOT', { nodeId, ...options });
|
|
208
|
+
}
|
|
209
|
+
async getSlots(nodeId) {
|
|
210
|
+
return this.sendCommand('GET_SLOTS', { nodeId });
|
|
211
|
+
}
|
|
212
|
+
async appendToSlot(params) {
|
|
213
|
+
return this.sendCommand('APPEND_TO_SLOT', params);
|
|
214
|
+
}
|
|
215
|
+
async resetSlot(params) {
|
|
216
|
+
return this.sendCommand('RESET_SLOT', params);
|
|
217
|
+
}
|
|
218
|
+
// ============================================================================
|
|
202
219
|
// Node manipulation
|
|
203
220
|
// ============================================================================
|
|
204
221
|
async resizeNode(nodeId, width, height, withConstraints = true) {
|
|
@@ -3229,7 +3229,7 @@ export function registerFigmaAPITools(server, getFigmaAPI, getCurrentUrl, variab
|
|
|
3229
3229
|
// Tool 16: Set Instance Properties (Desktop Bridge)
|
|
3230
3230
|
// Updates component properties on an instance using setProperties()
|
|
3231
3231
|
// This is the correct way to update TEXT/BOOLEAN/VARIANT properties on component instances
|
|
3232
|
-
server.tool("figma_set_instance_properties", "Update component properties on a component instance. IMPORTANT: Use this tool instead of trying to edit text nodes directly when working with component instances. Components often expose TEXT, BOOLEAN, INSTANCE_SWAP, and VARIANT properties that control their content. Direct text node editing may fail silently if the component uses properties. This tool handles the #nodeId suffix pattern automatically. Requires Desktop Bridge connection.", {
|
|
3232
|
+
server.tool("figma_set_instance_properties", "Update component properties on a component instance. IMPORTANT: Use this tool instead of trying to edit text nodes directly when working with component instances. Components often expose TEXT, BOOLEAN, INSTANCE_SWAP, and VARIANT properties that control their content. SLOT properties CANNOT be set here β use figma_append_to_slot to populate slot content. Direct text node editing may fail silently if the component uses properties. This tool handles the #nodeId suffix pattern automatically. Requires Desktop Bridge connection.", {
|
|
3233
3233
|
nodeId: z
|
|
3234
3234
|
.string()
|
|
3235
3235
|
.describe("ID of the INSTANCE node to update (e.g., '1:234'). Must be a component instance, not a regular frame."),
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { createChildLogger } from "./logger.js";
|
|
3
|
+
const logger = createChildLogger({ component: "slot-tools" });
|
|
4
|
+
const SLOT_LAYOUT_MODES = ["NONE", "HORIZONTAL", "VERTICAL"];
|
|
5
|
+
const SLOT_CHILD_NODE_TYPES = [
|
|
6
|
+
"FRAME",
|
|
7
|
+
"RECTANGLE",
|
|
8
|
+
"ELLIPSE",
|
|
9
|
+
"TEXT",
|
|
10
|
+
"LINE",
|
|
11
|
+
"POLYGON",
|
|
12
|
+
"STAR",
|
|
13
|
+
"VECTOR",
|
|
14
|
+
];
|
|
15
|
+
const preferredValueSchema = z.object({
|
|
16
|
+
type: z.enum(["COMPONENT", "COMPONENT_SET"]).describe("Type of preferred value"),
|
|
17
|
+
key: z.string().describe("Component or component set key"),
|
|
18
|
+
});
|
|
19
|
+
/**
|
|
20
|
+
* Register MCP tools for Figma Slots (open beta).
|
|
21
|
+
* Requires Desktop Bridge and a Figma Desktop version with SlotNode API support.
|
|
22
|
+
*/
|
|
23
|
+
export function registerSlotTools(server, getDesktopConnector) {
|
|
24
|
+
server.tool("figma_create_slot", `Create a SlotNode inside a component using createSlot(). Automatically creates a linked SLOT component property named after the slot (renaming the slot renames the property). Slots are freeform drop zones for instance content β more flexible than INSTANCE_SWAP. Works on standalone COMPONENTs and variant COMPONENTs inside a COMPONENT_SET (call once per variant). GRID layout is not allowed on slots. Requires Desktop Bridge.`, {
|
|
25
|
+
nodeId: z
|
|
26
|
+
.string()
|
|
27
|
+
.describe("The COMPONENT node ID to add a slot to"),
|
|
28
|
+
name: z
|
|
29
|
+
.string()
|
|
30
|
+
.optional()
|
|
31
|
+
.describe("Slot layer name (e.g. 'Content', 'Footer')"),
|
|
32
|
+
width: z
|
|
33
|
+
.number()
|
|
34
|
+
.optional()
|
|
35
|
+
.describe("Initial slot width in pixels"),
|
|
36
|
+
height: z
|
|
37
|
+
.number()
|
|
38
|
+
.optional()
|
|
39
|
+
.describe("Initial slot height in pixels"),
|
|
40
|
+
layoutMode: z
|
|
41
|
+
.enum(SLOT_LAYOUT_MODES)
|
|
42
|
+
.optional()
|
|
43
|
+
.describe("Auto-layout mode for the slot (GRID is not supported)"),
|
|
44
|
+
}, async ({ nodeId, name, width, height, layoutMode }) => {
|
|
45
|
+
try {
|
|
46
|
+
const connector = await getDesktopConnector();
|
|
47
|
+
const result = await connector.createSlot(nodeId, {
|
|
48
|
+
name,
|
|
49
|
+
width,
|
|
50
|
+
height,
|
|
51
|
+
layoutMode,
|
|
52
|
+
});
|
|
53
|
+
if (!result.success) {
|
|
54
|
+
throw new Error(result.error || "Failed to create slot");
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
content: [
|
|
58
|
+
{
|
|
59
|
+
type: "text",
|
|
60
|
+
text: JSON.stringify({
|
|
61
|
+
success: true,
|
|
62
|
+
slot: result.slot,
|
|
63
|
+
hint: "Use figma_get_slots to inspect slots. Populate instance slots with figma_append_to_slot (not figma_set_instance_properties).",
|
|
64
|
+
}),
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
logger.error({ error }, "figma_create_slot failed");
|
|
71
|
+
return {
|
|
72
|
+
content: [
|
|
73
|
+
{
|
|
74
|
+
type: "text",
|
|
75
|
+
text: JSON.stringify({
|
|
76
|
+
error: error instanceof Error ? error.message : String(error),
|
|
77
|
+
hint: "Target a COMPONENT node (standalone or a variant inside a COMPONENT_SET). Ensure Figma Desktop supports Slots.",
|
|
78
|
+
}),
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
isError: true,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
server.tool("figma_get_slots", `List SlotNode children on a component, component set, or instance. Returns slot IDs, names, property keys, dimensions, and current child nodes. Use on instances before figma_append_to_slot to discover slot names/IDs.`, {
|
|
86
|
+
nodeId: z
|
|
87
|
+
.string()
|
|
88
|
+
.describe("COMPONENT, COMPONENT_SET, or INSTANCE node ID"),
|
|
89
|
+
}, async ({ nodeId }) => {
|
|
90
|
+
try {
|
|
91
|
+
const connector = await getDesktopConnector();
|
|
92
|
+
const result = await connector.getSlots(nodeId);
|
|
93
|
+
if (!result.success) {
|
|
94
|
+
throw new Error(result.error || "Failed to get slots");
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
content: [
|
|
98
|
+
{
|
|
99
|
+
type: "text",
|
|
100
|
+
text: JSON.stringify({ success: true, ...(result.data ?? {}) }),
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
logger.error({ error }, "figma_get_slots failed");
|
|
107
|
+
return {
|
|
108
|
+
content: [
|
|
109
|
+
{
|
|
110
|
+
type: "text",
|
|
111
|
+
text: JSON.stringify({
|
|
112
|
+
error: error instanceof Error ? error.message : String(error),
|
|
113
|
+
hint: "Ensure the node is a component or instance with slots, and Desktop Bridge is connected.",
|
|
114
|
+
}),
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
isError: true,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
server.tool("figma_append_to_slot", `Add content to a slot on a component instance. Clones sourceNodeId into the slot, or creates a new node (nodeType). SLOT content cannot be set via figma_set_instance_properties β use this tool instead. Widgets, stickies, and raw ComponentNodes cannot be appended to slots.`, {
|
|
122
|
+
slotId: z
|
|
123
|
+
.string()
|
|
124
|
+
.optional()
|
|
125
|
+
.describe("Direct SlotNode ID (from figma_get_slots)"),
|
|
126
|
+
instanceId: z
|
|
127
|
+
.string()
|
|
128
|
+
.optional()
|
|
129
|
+
.describe("Instance ID β use with slotName when slotId is unknown"),
|
|
130
|
+
slotName: z
|
|
131
|
+
.string()
|
|
132
|
+
.optional()
|
|
133
|
+
.describe("Slot layer name on the instance (e.g. 'Content')"),
|
|
134
|
+
sourceNodeId: z
|
|
135
|
+
.string()
|
|
136
|
+
.optional()
|
|
137
|
+
.describe("Node to clone into the slot (default: clone=true)"),
|
|
138
|
+
nodeType: z
|
|
139
|
+
.enum(SLOT_CHILD_NODE_TYPES)
|
|
140
|
+
.optional()
|
|
141
|
+
.describe("Create a new node in the slot instead of cloning"),
|
|
142
|
+
properties: z
|
|
143
|
+
.record(z.string(), z.union([z.string(), z.number()]))
|
|
144
|
+
.optional()
|
|
145
|
+
.describe("Properties for created nodes: name, text, width, height"),
|
|
146
|
+
clone: z
|
|
147
|
+
.boolean()
|
|
148
|
+
.optional()
|
|
149
|
+
.default(true)
|
|
150
|
+
.describe("When using sourceNodeId, clone the node (default true). Set false to move."),
|
|
151
|
+
clearExisting: z
|
|
152
|
+
.boolean()
|
|
153
|
+
.optional()
|
|
154
|
+
.default(false)
|
|
155
|
+
.describe("Remove existing slot children before appending"),
|
|
156
|
+
}, async (args) => {
|
|
157
|
+
try {
|
|
158
|
+
if (!args.slotId && !(args.instanceId && args.slotName)) {
|
|
159
|
+
throw new Error("Provide slotId OR (instanceId + slotName)");
|
|
160
|
+
}
|
|
161
|
+
if (!args.sourceNodeId && !args.nodeType) {
|
|
162
|
+
throw new Error("Provide sourceNodeId (clone into slot) or nodeType (create new content)");
|
|
163
|
+
}
|
|
164
|
+
const connector = await getDesktopConnector();
|
|
165
|
+
const result = await connector.appendToSlot(args);
|
|
166
|
+
if (!result.success) {
|
|
167
|
+
throw new Error(result.error || "Failed to append to slot");
|
|
168
|
+
}
|
|
169
|
+
return {
|
|
170
|
+
content: [
|
|
171
|
+
{
|
|
172
|
+
type: "text",
|
|
173
|
+
text: JSON.stringify({
|
|
174
|
+
success: true,
|
|
175
|
+
slot: result.slot,
|
|
176
|
+
appendedNode: result.appendedNode,
|
|
177
|
+
hint: "Use figma_capture_screenshot to verify slot content visually.",
|
|
178
|
+
}),
|
|
179
|
+
},
|
|
180
|
+
],
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
logger.error({ error }, "figma_append_to_slot failed");
|
|
185
|
+
return {
|
|
186
|
+
content: [
|
|
187
|
+
{
|
|
188
|
+
type: "text",
|
|
189
|
+
text: JSON.stringify({
|
|
190
|
+
error: error instanceof Error ? error.message : String(error),
|
|
191
|
+
hints: [
|
|
192
|
+
"Use figma_get_slots on the instance to find slot IDs/names",
|
|
193
|
+
"Clone instances or frames β not raw ComponentNodes",
|
|
194
|
+
"SLOT properties cannot be set via figma_set_instance_properties",
|
|
195
|
+
],
|
|
196
|
+
}),
|
|
197
|
+
},
|
|
198
|
+
],
|
|
199
|
+
isError: true,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
server.tool("figma_reset_slot", `Reset a slot on a component instance to its default (empty) state from the main component. Uses SlotNode.resetSlot().`, {
|
|
204
|
+
slotId: z
|
|
205
|
+
.string()
|
|
206
|
+
.optional()
|
|
207
|
+
.describe("Direct SlotNode ID"),
|
|
208
|
+
instanceId: z
|
|
209
|
+
.string()
|
|
210
|
+
.optional()
|
|
211
|
+
.describe("Instance ID β use with slotName when slotId is unknown"),
|
|
212
|
+
slotName: z
|
|
213
|
+
.string()
|
|
214
|
+
.optional()
|
|
215
|
+
.describe("Slot layer name on the instance"),
|
|
216
|
+
}, async ({ slotId, instanceId, slotName }) => {
|
|
217
|
+
try {
|
|
218
|
+
if (!slotId && !(instanceId && slotName)) {
|
|
219
|
+
throw new Error("Provide slotId OR (instanceId + slotName)");
|
|
220
|
+
}
|
|
221
|
+
const connector = await getDesktopConnector();
|
|
222
|
+
const result = await connector.resetSlot({ slotId, instanceId, slotName });
|
|
223
|
+
if (!result.success) {
|
|
224
|
+
throw new Error(result.error || "Failed to reset slot");
|
|
225
|
+
}
|
|
226
|
+
return {
|
|
227
|
+
content: [
|
|
228
|
+
{
|
|
229
|
+
type: "text",
|
|
230
|
+
text: JSON.stringify({
|
|
231
|
+
success: true,
|
|
232
|
+
slot: result.slot,
|
|
233
|
+
}),
|
|
234
|
+
},
|
|
235
|
+
],
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
catch (error) {
|
|
239
|
+
logger.error({ error }, "figma_reset_slot failed");
|
|
240
|
+
return {
|
|
241
|
+
content: [
|
|
242
|
+
{
|
|
243
|
+
type: "text",
|
|
244
|
+
text: JSON.stringify({
|
|
245
|
+
error: error instanceof Error ? error.message : String(error),
|
|
246
|
+
}),
|
|
247
|
+
},
|
|
248
|
+
],
|
|
249
|
+
isError: true,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
server.tool("figma_add_slot_property", `Manually add a SLOT component property and bind it to an existing frame (alternative to figma_create_slot). The frame must be a direct child of the component, must not use GRID layout, and must not be nested inside another slot. Supports description and preferredValues.`, {
|
|
254
|
+
nodeId: z.string().describe("COMPONENT or COMPONENT_SET node ID"),
|
|
255
|
+
propertyName: z.string().describe("Slot property name (e.g. 'Content')"),
|
|
256
|
+
frameNodeId: z
|
|
257
|
+
.string()
|
|
258
|
+
.describe("Frame node ID to bind as the slot content area"),
|
|
259
|
+
description: z
|
|
260
|
+
.string()
|
|
261
|
+
.optional()
|
|
262
|
+
.describe("Slot property description (SLOT-only)"),
|
|
263
|
+
preferredValues: z
|
|
264
|
+
.array(preferredValueSchema)
|
|
265
|
+
.optional()
|
|
266
|
+
.describe("Preferred components for slot content (SLOT-only)"),
|
|
267
|
+
}, async ({ nodeId, propertyName, frameNodeId, description, preferredValues }) => {
|
|
268
|
+
try {
|
|
269
|
+
const connector = await getDesktopConnector();
|
|
270
|
+
const code = `
|
|
271
|
+
const component = await figma.getNodeByIdAsync(${JSON.stringify(nodeId)});
|
|
272
|
+
if (!component || (component.type !== 'COMPONENT' && component.type !== 'COMPONENT_SET')) {
|
|
273
|
+
throw new Error('Node must be COMPONENT or COMPONENT_SET');
|
|
274
|
+
}
|
|
275
|
+
const frame = await figma.getNodeByIdAsync(${JSON.stringify(frameNodeId)});
|
|
276
|
+
if (!frame || frame.type !== 'FRAME') {
|
|
277
|
+
throw new Error('frameNodeId must be a FRAME node');
|
|
278
|
+
}
|
|
279
|
+
// For a COMPONENT the frame must be its direct child. For a COMPONENT_SET the
|
|
280
|
+
// property lives on the set but the frame lives inside a variant COMPONENT β
|
|
281
|
+
// accept a frame whose parent is a variant of this set.
|
|
282
|
+
const directChild = frame.parent === component;
|
|
283
|
+
const variantChild = component.type === 'COMPONENT_SET' &&
|
|
284
|
+
frame.parent && frame.parent.type === 'COMPONENT' && frame.parent.parent === component;
|
|
285
|
+
if (!directChild && !variantChild) {
|
|
286
|
+
throw new Error(component.type === 'COMPONENT_SET'
|
|
287
|
+
? 'Frame must be a direct child of one of the set\\'s variant components'
|
|
288
|
+
: 'Frame must be a direct child of the component');
|
|
289
|
+
}
|
|
290
|
+
if (frame.layoutMode === 'GRID') {
|
|
291
|
+
throw new Error('GRID layoutMode is not allowed on slot frames');
|
|
292
|
+
}
|
|
293
|
+
const options = {};
|
|
294
|
+
${description ? `options.description = ${JSON.stringify(description)};` : ""}
|
|
295
|
+
${preferredValues ? `options.preferredValues = ${JSON.stringify(preferredValues)};` : ""}
|
|
296
|
+
const propKey = component.addComponentProperty(${JSON.stringify(propertyName)}, 'SLOT', '', Object.keys(options).length ? options : undefined);
|
|
297
|
+
// Merge β assigning a fresh object would wipe the frame's existing bindings
|
|
298
|
+
// (e.g. a BOOLEAN property driving 'visible').
|
|
299
|
+
frame.componentPropertyReferences = Object.assign({}, frame.componentPropertyReferences, { slotContentId: propKey });
|
|
300
|
+
return { propertyKey: propKey, frameId: frame.id, frameName: frame.name };
|
|
301
|
+
`;
|
|
302
|
+
const result = await connector.executeCodeViaUI(code, 10000);
|
|
303
|
+
if (!result.success) {
|
|
304
|
+
throw new Error(result.error || "Failed to add slot property");
|
|
305
|
+
}
|
|
306
|
+
return {
|
|
307
|
+
content: [
|
|
308
|
+
{
|
|
309
|
+
type: "text",
|
|
310
|
+
text: JSON.stringify({
|
|
311
|
+
success: true,
|
|
312
|
+
result: result.result,
|
|
313
|
+
hint: "Prefer figma_create_slot() for new slots β it creates the SlotNode and property automatically.",
|
|
314
|
+
}),
|
|
315
|
+
},
|
|
316
|
+
],
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
catch (error) {
|
|
320
|
+
logger.error({ error }, "figma_add_slot_property failed");
|
|
321
|
+
return {
|
|
322
|
+
content: [
|
|
323
|
+
{
|
|
324
|
+
type: "text",
|
|
325
|
+
text: JSON.stringify({
|
|
326
|
+
error: error instanceof Error ? error.message : String(error),
|
|
327
|
+
}),
|
|
328
|
+
},
|
|
329
|
+
],
|
|
330
|
+
isError: true,
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
}
|
|
@@ -35,7 +35,7 @@ const logger = createChildLogger({ component: "tokens-tools" });
|
|
|
35
35
|
* on every exported token document. Kept in sync with package.json by
|
|
36
36
|
* scripts/release.sh β see step 3 of the release flow.
|
|
37
37
|
*/
|
|
38
|
-
const MCP_VERSION = "1.
|
|
38
|
+
const MCP_VERSION = "1.35.0";
|
|
39
39
|
const EXPORT_TOOL_DESCRIPTION = `Export Figma variables to design token files in your codebase. Bidirectional with figma_import_tokens β together they replace Style Dictionary and Tokens Studio's export pipeline for the popular styling methods.
|
|
40
40
|
|
|
41
41
|
FULLY-IMPLEMENTED OUTPUT FORMATS:
|
|
@@ -196,6 +196,8 @@ export class WebSocketConnector {
|
|
|
196
196
|
const params = { nodeId, propertyName, propertyType: type, defaultValue };
|
|
197
197
|
if (options?.preferredValues)
|
|
198
198
|
params.preferredValues = options.preferredValues;
|
|
199
|
+
if (options?.description)
|
|
200
|
+
params.description = options.description;
|
|
199
201
|
return this.wsServer.sendCommand('ADD_COMPONENT_PROPERTY', params);
|
|
200
202
|
}
|
|
201
203
|
async editComponentProperty(nodeId, propertyName, newValue) {
|
|
@@ -231,6 +233,21 @@ export class WebSocketConnector {
|
|
|
231
233
|
return this.wsServer.sendCommand('CREATE_COMPONENT_SET', params, componentSetTimeoutMs(params));
|
|
232
234
|
}
|
|
233
235
|
// ============================================================================
|
|
236
|
+
// Slot operations
|
|
237
|
+
// ============================================================================
|
|
238
|
+
async createSlot(nodeId, options) {
|
|
239
|
+
return this.wsServer.sendCommand('CREATE_SLOT', { nodeId, ...options });
|
|
240
|
+
}
|
|
241
|
+
async getSlots(nodeId) {
|
|
242
|
+
return this.wsServer.sendCommand('GET_SLOTS', { nodeId });
|
|
243
|
+
}
|
|
244
|
+
async appendToSlot(params) {
|
|
245
|
+
return this.wsServer.sendCommand('APPEND_TO_SLOT', params);
|
|
246
|
+
}
|
|
247
|
+
async resetSlot(params) {
|
|
248
|
+
return this.wsServer.sendCommand('RESET_SLOT', params);
|
|
249
|
+
}
|
|
250
|
+
// ============================================================================
|
|
234
251
|
// Node manipulation
|
|
235
252
|
// ============================================================================
|
|
236
253
|
async resizeNode(nodeId, width, height, withConstraints = true) {
|
|
@@ -1076,21 +1076,52 @@ After instantiating components, use figma_take_screenshot to verify the result l
|
|
|
1076
1076
|
}
|
|
1077
1077
|
});
|
|
1078
1078
|
// Tool: Add Component Property
|
|
1079
|
-
server.tool("figma_add_component_property", "Add a new component property to a component or component set. Properties enable dynamic content and behavior in component instances. Supported types: BOOLEAN (toggle), TEXT (string), INSTANCE_SWAP (component swap), VARIANT (variant selection).", {
|
|
1079
|
+
server.tool("figma_add_component_property", "Add a new component property to a component or component set. Properties enable dynamic content and behavior in component instances. Supported types: BOOLEAN (toggle), TEXT (string), INSTANCE_SWAP (component swap), VARIANT (variant selection), SLOT (freeform slot β prefer figma_create_slot for new slots).", {
|
|
1080
1080
|
nodeId: z.string().describe("The component or component set node ID"),
|
|
1081
1081
|
propertyName: z
|
|
1082
1082
|
.string()
|
|
1083
|
-
.describe("Name for the new property (e.g., 'Show Icon', 'Button Label')"),
|
|
1083
|
+
.describe("Name for the new property (e.g., 'Show Icon', 'Button Label', 'Content')"),
|
|
1084
1084
|
type: z
|
|
1085
|
-
.enum(["BOOLEAN", "TEXT", "INSTANCE_SWAP", "VARIANT"])
|
|
1086
|
-
.describe("Property type: BOOLEAN for toggles, TEXT for strings, INSTANCE_SWAP for component swaps, VARIANT for variant selection"),
|
|
1085
|
+
.enum(["BOOLEAN", "TEXT", "INSTANCE_SWAP", "VARIANT", "SLOT"])
|
|
1086
|
+
.describe("Property type: BOOLEAN for toggles, TEXT for strings, INSTANCE_SWAP for component swaps, VARIANT for variant selection, SLOT for freeform slot areas"),
|
|
1087
1087
|
defaultValue: z
|
|
1088
1088
|
.union([z.string(), z.number(), z.boolean()])
|
|
1089
|
-
.
|
|
1090
|
-
|
|
1089
|
+
.optional()
|
|
1090
|
+
.describe("Default value. Required for TEXT (string), INSTANCE_SWAP (component id), and VARIANT (a non-empty variant option name β Figma rejects empty). Optional for BOOLEAN (defaults false). Omit for SLOT."),
|
|
1091
|
+
description: z
|
|
1092
|
+
.string()
|
|
1093
|
+
.optional()
|
|
1094
|
+
.describe("Property description (SLOT properties only)"),
|
|
1095
|
+
preferredValues: z
|
|
1096
|
+
.array(z.object({
|
|
1097
|
+
type: z.enum(["COMPONENT", "COMPONENT_SET"]),
|
|
1098
|
+
key: z.string(),
|
|
1099
|
+
}))
|
|
1100
|
+
.optional()
|
|
1101
|
+
.describe("Preferred components (INSTANCE_SWAP and SLOT only)"),
|
|
1102
|
+
}, async ({ nodeId, propertyName, type, defaultValue, description, preferredValues }) => {
|
|
1091
1103
|
try {
|
|
1092
1104
|
const connector = await getDesktopConnector();
|
|
1093
|
-
const
|
|
1105
|
+
const options = {};
|
|
1106
|
+
if (description)
|
|
1107
|
+
options.description = description;
|
|
1108
|
+
if (preferredValues)
|
|
1109
|
+
options.preferredValues = preferredValues;
|
|
1110
|
+
// SLOT is the only type that takes no default. VARIANT requires a
|
|
1111
|
+
// non-empty default (Figma rejects ''), and TEXT/INSTANCE_SWAP fail
|
|
1112
|
+
// deep in the plugin with opaque errors when the default is missing β
|
|
1113
|
+
// validate here so the caller gets an actionable message.
|
|
1114
|
+
if ((type === "TEXT" || type === "INSTANCE_SWAP" || type === "VARIANT") &&
|
|
1115
|
+
(defaultValue === undefined || defaultValue === "")) {
|
|
1116
|
+
throw new Error(`defaultValue is required for ${type} properties` +
|
|
1117
|
+
(type === "INSTANCE_SWAP"
|
|
1118
|
+
? " (a component id)"
|
|
1119
|
+
: type === "VARIANT"
|
|
1120
|
+
? " (a non-empty variant option name)"
|
|
1121
|
+
: ""));
|
|
1122
|
+
}
|
|
1123
|
+
const resolvedDefault = type === "SLOT" ? "" : (defaultValue ?? (type === "BOOLEAN" ? false : ""));
|
|
1124
|
+
const result = await connector.addComponentProperty(nodeId, propertyName, type, resolvedDefault, Object.keys(options).length > 0 ? options : undefined);
|
|
1094
1125
|
if (!result.success) {
|
|
1095
1126
|
throw new Error(result.error || "Failed to add property");
|
|
1096
1127
|
}
|
|
@@ -1184,7 +1215,7 @@ After instantiating components, use figma_take_screenshot to verify the result l
|
|
|
1184
1215
|
}
|
|
1185
1216
|
});
|
|
1186
1217
|
// Tool: Delete Component Property
|
|
1187
|
-
server.tool("figma_delete_component_property", "Delete a component property.
|
|
1218
|
+
server.tool("figma_delete_component_property", "Delete a component property. Works with BOOLEAN, TEXT, INSTANCE_SWAP, and SLOT properties (not VARIANT). This is a destructive operation.", {
|
|
1188
1219
|
nodeId: z.string().describe("The component or component set node ID"),
|
|
1189
1220
|
propertyName: z
|
|
1190
1221
|
.string()
|
|
@@ -1216,7 +1247,7 @@ After instantiating components, use figma_take_screenshot to verify the result l
|
|
|
1216
1247
|
type: "text",
|
|
1217
1248
|
text: JSON.stringify({
|
|
1218
1249
|
error: error instanceof Error ? error.message : String(error),
|
|
1219
|
-
hint: "Cannot delete VARIANT properties.
|
|
1250
|
+
hint: "Cannot delete VARIANT properties. BOOLEAN, TEXT, INSTANCE_SWAP, and SLOT can be deleted.",
|
|
1220
1251
|
}),
|
|
1221
1252
|
},
|
|
1222
1253
|
],
|