This release restores type-checking for the Figma plugin and closes the v2.3.3 safety-contract gaps across design-system writes, explicit placement, batch reporting, annotations, structured errors, peer-bound channels, and page-load isolation. It is a hard contract cutover: fail-closed safety repairs ship at patch level, and callers must migrate the request and response shapes below.
💥 Breaking changes (migration required)
- Design-system writes require current-name/collection verification.
UPDATE_VARIABLEneedscurrentVariableName;style_manageid-based updates needcurrentStyleName;CREATE_VARIABLEneedscollectionNameand an explicitscopesarray. - Every caller-placed creator needs an explicit verified parent.
create_shape,create_frame,create_text,create_svg,create_instance, and nowcreate_component_setrequireparentId+parentNodeName. annotation_setis a working append contract. Each row needsnodeId,nodeName, and a non-blanklabelMarkdown;annotationId/statusare gone;propertiesis an array of{type}entries.annotation_listreturns one grouped shape in both page and node modes — node mode no longer returns a flat owner-tagged list.- Nested inputs are strict. Unknown keys are rejected at any object depth (two intentional pass-throughs remain:
style_manage.properties.paints[]and.layoutGrids[]). - Effect payloads are strict per variant.
style_manage.properties.effects[]enumerates each Figma effect type with variant-specific fields and the exactBlendModeset; cross-variant keys are rejected instead of silently dropped. - Batch validation rejects empty/duplicate target sets up front, and the four batch aggregators (
node_delete,text_set_content,annotation_set,instance_set_overrides) now share one result envelope:success,status,requestedCount,succeededCount,failedCount,skippedCount, and one row per input. - The socket handshake is role-declared, versioned, and peer-bound. Joins now send
clientTypeand a version; the server can refuse a missing plugin, a duplicate MCP session, or a version mismatch. - The connector-visualization surface is removed —
create_connectionand its FigJam-diagram output are gone; usereaction_list/reaction_updatefor native prototype metadata. - Empty-name assignment is rejected wherever Figma can't honor it truthfully — covers
node_rename,node_group, the five creators,create_component_set,variable_manage,style_manage, andcomponent_manage_property. variable_deletein-use failures are a thrown, coded error (VARIABLE_IN_USE), with consumer evidence undererror.details.variablesInUse, instead of a successful result carrying a prose string.- Page coverage is explicit. Every
coverageobject requirespagesAttempted;node_infoaddspageFailedNodes;page_info.missingPageIdsnow includes load/read failures.
✨ Added
- Structured
{code, message, details}errors carried through the MCP boundary end-to-end. create_instanceresponses include the resolvedcomponentId.- Plugin type-check gate: strict TypeScript (no
domlib) plus a CI-enforced name-assignment oracle, so a new naming API can't be added without its empty-name contract. - Page-load isolation: destructive variable scans refuse with
DOCUMENT_SCAN_INCOMPLETEunless every required page was inspected; successful page data survives partial failures elsewhere. node_flattenreports the containingparentId, matching the other creators.- Script type-check gate in CI (
check:types:scripts).
🩹 Fixed
annotation_setcan append more than once to the same node — appending previously rewrote the existing entry into a shape Figma's own setter rejects, so only the first annotation on a node could ever be created.variable_deleteno longer false-positivesDOCUMENT_SCAN_INCOMPLETEon a healthy document — its consumer scan now loads pages sequentially instead of concurrently.- Percent-encoded
node-idscope links resolve in the plugin UI (the Figma sandbox has noURLglobal, so the parser's regex fallback never decoded them). node_deleterows for a target already removed alongside its ancestor in the same batch now say so, instead of telling the caller to retry a call the dispatcher refuses.annotation_set.properties[]rejections explain that validity is node-type-dependent and how to resend only the failed rows.
🔧 Changed
- Implicit creators, clone, flatten, and component/component-set creation place results at the verified destination by an observable success boundary; cleanup after a later failure is best-effort, not an infallible rollback (supersedes v2.3.2's "no-orphan creation" wording).
- Design-system updates validate their full plan before the first mutation and disclose unexpected mid-update mutations.
- Effect numeric bounds now match what Figma actually preserves (e.g. NOISE
density0–1;noiseSize/TEXTUREradius0–100; GLASSdepth >= 1; at most one GLASS effect per node).
⚠️ Notes
- Registered MCP inventory: 45 tools.
- Backwards compatibility is not a concern for this project (no external end-users), so these contract changes ship without a deprecation cycle.
Details: see CHANGELOG.md and PR #49.
What's Changed
- v2.3.3: Restore plugin type-checking; close safety-contract gaps by @neozhehan in #49
- Update node version to 24 by @neozhehan in #50
Full Changelog: v2.3.2...v2.3.3