Skip to content

add write tools — find_nodes and batch_mutation - #29

Open
matiukhov wants to merge 48 commits into
gethopp:mainfrom
matiukhov:main
Open

add write tools — find_nodes and batch_mutation#29
matiukhov wants to merge 48 commits into
gethopp:mainfrom
matiukhov:main

Conversation

@matiukhov

Copy link
Copy Markdown

Adds two new MCP tools that enable write operations on Figma files through the bridge:

  • find_nodes — query nodes on the current page by id, name, key, parent, or substring
  • batch_mutation — execute an ordered sequence of write operations (create frames/text/shapes, set properties, delete nodes, etc.) in a single call

Changes

  • plugin/src/main/write.ts — plugin-side implementation of all supported mutation operations
  • server/src/schema.ts — Zod schemas for find_nodes, batch_mutation, and the batchOperationType enum
  • server/src/tools.ts — registers the two new tools via a typed registerWriteTool helper; improves bridge error formatting
  • plugin/tests/write.test.mjs — test coverage for write operations
  • README.md — updated docs

konsalex and others added 30 commits February 7, 2026 20:04
fix: ensure `paints` is array when serialize
feat(server): add save_screenshots tool for batch export to disk
Add missing text style properties to the plugin's node serializer and
text style handler. This enables MCP consumers to distinguish font
weights, decorations, and spacing when reading Figma text layers.

- Add fontWeight, fontStyle, textDecoration, lineHeight, letterSpacing
  to serializeText in serializer.ts
- Add serializeLineHeight and serializeLetterSpacing helpers to handle
  the LineHeight discriminated union (AUTO vs explicit) and mixed values
- Add textDecoration, lineHeight, letterSpacing to get_styles text
  style output in code.ts
fix: allow in dev mode, and hopp branding
feat: add fontWeight, fontStyle, and other text properties to serializer
…h-tools

feat: add more styles in payload and readme update
Replace single WebSocket connection with a Map keyed by fileKey,
allowing multiple Figma files to be open and queryable at the same time.

- Bridge stores connections per fileKey extracted from WS query params
- Plugin sends figma.fileKey when connecting to the WebSocket server
- All tools accept optional fileKey param to target a specific file
- New list_files tool to discover connected files
- Backward compatible: single-file setups work without specifying fileKey
feat: support multiple Figma files connected simultaneously
The leader forwards rpcReq.nodeIds to the bridge, so an RPC call with only
params.nodeId would validate locally but fail downstream when nodeIds is
empty. Spread params first, then set nodeId from nodeIds?.[0] so the
forwarded payload always wins.
startsWith("/ws") accepted unintended paths like /ws-anything. Parse the
URL and compare pathname so query parameters still work without widening
the upgrade endpoint.
Pending requests previously waited the full 30-second timeout when a
plugin connection dropped, then surfaced a misleading "Request timed out"
error. Tag each PendingRequest with its WebSocket so close/error handlers
can eagerly reject the right subset with a clear "plugin disconnected"
message — the agent learns immediately and gets the actual cause.
…llback

figma.fileKey is null for unsaved files, and the previous fallback to
figma.root.name collided whenever two files shared a name. Generate a
session-scoped fallback key on first use and emit a console.warn so
issues are easy to diagnose from a built plugin.

Addresses konsalex's PR gethopp#20 review feedback (the request for an error log
on the fallback path) and CodeRabbit's note that the fallback identifier
needs to be unique.
When status.fileKey or status.fileName changes, cleanup closed the old
socket but its onclose handler could still schedule connect() through a
stale closure — that timer would later close the new socket and reconnect
to the previous file.

Add a disposed flag and guard onclose/onerror against running for a
socket that has been replaced. Clear the event handlers in cleanup so
in-flight events on the closing socket cannot mutate React state.
…wups

fix: address PR gethopp#20 review feedback (multi-file)
vladmdgolam and others added 18 commits May 24, 2026 23:03
Allows toggling visibility of any Figma nodes via MCP. Returns previous
visibility state so callers can restore it — enabling workflows like
hiding all siblings, exporting an isolated layer, then restoring.
Create image-backed rectangles from local paths, URLs, or data URIs.
New MCP tool that replaces a node's fill (or stroke) with a
LINEAR/RADIAL/ANGULAR/DIAMOND gradient paint. Accepts ordered stops
({position, hex, opacity?}) and an optional 2x3 gradientTransform
matching Figma's format. Lets agents apply gradient fills the same
way they already apply solid fills via set_node_properties.
Dev Mode is read-only — every figma.create*/setter would otherwise throw a
confusing low-level error from inside the Figma sandbox. Reject mutating
requests up front with a clear message that points the user to switch to
the design editor. Read-only tools continue to work in Dev Mode unchanged.
Move the solidFillHex/solidFillOpacity fields out of set_node_properties
into a dedicated set_solid_fill tool, mirroring set_gradient_fill. The
new tool takes { hex, opacity?, target? } where target is "fill" or
"stroke", matching the gradient API. set_node_properties now only
patches geometry/identity properties; paint changes go through
set_solid_fill or set_gradient_fill.
The handler previously only applied stroke params for LINE shapes —
RECTANGLE and ELLIPSE silently dropped strokeHex/strokeOpacity/
strokeWeight even though the schema accepted them. It also defaulted
LINE strokeHex to "#000000" implicitly, which was a hidden footgun
asymmetric with the other shape types.

Changes:
- Apply strokes uniformly for all shape types (reuses setSolidFill
  with target="stroke")
- Reject fillHex on LINE (Figma lines have no fill)
- Require strokeHex on LINE (otherwise invisible) — both at schema
  level via refine() and at handler level for clear errors
Close the biggest read/write asymmetries — these properties were already
emitted by the serializer (effects, stroke geometry, auto-layout config)
but no tool could write them.

set_effects replaces the full effects array (pass [] to clear), matching
Figma's array semantics. Shadow entries take {color, opacity, offset,
radius, spread, blendMode, visible}; blur entries take {radius, visible}.

set_stroke_properties patches geometry-only stroke fields (weight, align,
dashPattern, cap, join). Paint is still set via set_solid_fill or
set_gradient_fill with target='stroke'.

set_auto_layout configures one frame's layout: direction, gap, padding,
alignment, sizing modes, wrap. Set layoutMode='NONE' to disable.

All three are gated by EDIT_REQUEST_TYPES so they fail fast with a clear
error in Dev Mode.
Four small tools that close obvious gaps:

- group_nodes wraps nodes sharing a parent in a new group (or uses an
  explicit parentId). Returns the new group's id + child ids.
- ungroup_node explodes a GROUP or FRAME back to its parent and returns
  the orphans' ids in their new location.
- set_selection sets the page selection by node ids (pass [] to clear).
  Read-mode safe — works in Dev Mode without the editor-mode gate.
- scroll_and_zoom_into_view frames the viewport around the given nodes.
  Also Dev-Mode-safe.

group_nodes/ungroup_node are added to EDIT_REQUEST_TYPES so they fail
fast in Dev Mode. set_selection/scroll_and_zoom_into_view intentionally
skip the gate so an agent can still direct attention in read-only files.
The Zod regex only matched `^\d+:\d+$`, which rejected valid IDs the
plugin already returns from get_selection / get_design_context for
nodes inside an INSTANCE (e.g. `I12740:17806;12740:17793`). The bridge
was emitting these IDs and then rejecting them on the very next
get_node / get_screenshot / save_screenshots call, making instance
children unreachable.

Widen the regex to accept both forms; reject bare `I<id>` without a
`;<child>` segment since the `I` prefix is only meaningful with a
child segment. Update the corresponding `.describe(...)` strings and
the get_node tool description so model clients know the form is
allowed (the old text said "never use hyphens / colon format only",
which actively misled the model away from the second form).

Fixes gethopp#24
fix(schema): accept instance-child node IDs in figmaNodeId
feat: authoring/edit tools — set_*, create_*, duplicate/reparent/delete
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants