feat(figma): add Figma MCP for design-to-code context

Merged
YU
6d ago
+3539-0

Why

Most Studio tasks link a Figma frame, but the board agents have no access to it. Figma only offers its official MCP to approved clients. So the agents guess, and the result often does not match the design. This MCP reads designs through the public REST API with each user's own Figma account (OAuth). It gives agents a real reference: a screenshot, the layout as CSS, the tokens and the designer's comments.

image

What

New figma/ MCP. Tool names follow Figma's official MCP server, so prompts and skills written for it carry over.

image
  • Design to code: get_design_context (start here), get_metadata, get_screenshot, download_assets, get_variable_defs, get_motion_context, get_figjam, get_comments
  • Design systems: get_libraries, search_design_system
  • Code Connect: get_code_connect_map, add_code_connect_map, get_code_connect_suggestions, get_context_for_code_connect. Mappings are stored as Figma dev resources, because Figma's own Code Connect data is not in the public API.
  • whoami, plus the create_design_system_rules prompt

get_design_context returns the PNG of the node and one line per layer:

FRAME "Card" [1:1] 320×200 styles{fill:"Surface/White"} { display: flex; flex-direction: column; align-items: center; gap: 12px; padding: 16px 24px; background: #FFFFFF; border-radius: 8px }
  TEXT "Title" [1:2] 272×24 { align-self: stretch; color: #000000; font-family: "Inter"; font-weight: 700; font-size: 20px; line-height: 24px } "Tênis 574"
  FRAME "Icon" [1:3] 24×24 asset=svg { width: 24px; height: 24px }
  INSTANCE "Buy" [1:6] 272×48 <Button Label="Comprar" Size="Large"> { align-self: stretch }

The output also lists the unresolved comments pinned inside the frame. Briefs often say these are part of the spec.

Not included, by design: use_figma, generate_figma_design, create_new_file, upload_assets and generate_diagram. Creating or editing layers needs the Plugin API inside an open Figma client, and REST does not offer it. generate_image already exists in Studio.

https://github.com/user-attachments/assets/170a6422-fc77-4e3e-9b7a-f6f356685979

Deploy and auth (needs a reviewer)

  • Deploy: kubernetes-bun, entry added to deploy.json, served at https://sites-figma.deco.site/mcp.
  • Auth exemption: figma is added to auth-exemptions.json, which needs reviewer sign-off. The Authorization header carries the caller's own Figma token, and every tool forwards it to api.figma.com. There is no shared secret to check and no server-side credential exposed. This is the same situation as dynamic-yield.
  • OAuth hardening: runtime ^3.0.0 with stateSecret. The server refuses to boot without OAUTH_STATE_SECRET, so the code that carries the token is never plain base64. allowedRedirectHosts is pinned to decocms.com (plus EXTRA_ALLOWED_REDIRECT_HOSTS).
  • Personal tokens: a personal access token (figd_…) in the connection's Token field also works. It is sent as X-Figma-Token.

Before merging

  1. Create the Figma OAuth app with callback https://sites-figma.deco.site/oauth/callback and the scopes listed in figma/README.md. A private app only accepts users from the Figma org that owns it; a public app goes through Figma review.
  2. Provision these site secrets: FIGMA_CLIENT_ID, FIGMA_CLIENT_SECRET, OAUTH_STATE_SECRET.
  3. Rate limits follow the connected seat. View and Collab seats get only about 20 Tier-1 reads per month. Dev and Full seats get 10 to 20 per minute. See the README.

Verified

  • bun test in figma/: 10 tests pass (URL parsing, CSS conversion, truncation, XML output, comment scoping).
  • tsc --noEmit is clean for figma/server/**. The errors inside @decocms/runtime@3.0.0 sources also show up for github/ and come from how dependencies were installed in this environment.
  • check:auth, build:registry and the pre-commit format and lint checks pass.
  • Local boot of the built dist/server/main.js:
    • /.well-known/oauth-authorization-server is served.
    • tools/list returns the 15 tools and prompts/list returns the prompt.
    • /authorize redirects to figma.com/oauth with sealed state, and a non-deco redirect_uri is rejected.
    • A tool call reaches api.figma.com with the forwarded token.
  • Not yet verified against a real Figma file. That needs the OAuth app, or a personal token from a Dev or Full seat.

🤖 Generated with Claude Code


Summary by cubic

Adds a Figma MCP server so coding agents get real design context — screenshot, layout as flexbox CSS, tokens, comments, library search and Code Connect — read through the public REST API with each user's own Figma account (OAuth). Tool names mirror Figma's official MCP server, so prompts and skills written for it carry over.

The server is read-only: creating or editing layers is not included because the REST API doesn't offer it. Code Connect mappings are stored as Figma dev resources. get_design_context output was refined from a real run: LINE layers render as border-top dividers with the stroke color, text shows its content instead of stale layer names, fonts in use are listed so missing ones get flagged, and layout noise (gaps on single-child frames, missing position: relative) is dropped.

Reviewer notes

  • figma is added to auth-exemptions.json: every tool forwards the caller's own Figma token in the Authorization header, so there is no shared secret to check and no server-side credential to protect. OAuth state and code are sealed with OAUTH_STATE_SECRET, and redirect hosts are pinned to decocms.com.
  • Before merging, create the Figma OAuth app with the callback and scopes listed in figma/README.md and provision FIGMA_CLIENT_ID, FIGMA_CLIENT_SECRET and OAUTH_STATE_SECRET as site secrets. Until the app is approved, only its owner can authorize, so users connect with a personal access token (figd_…) through a Custom Connection; a Bearer prefix on the token is stripped before detection. Note rate limits: View and Collab seats get about 20 Tier-1 reads per month; Dev and Full seats get 10–20 per minute.
  • An explicit screenshot scale is honored even when the node size is unknown, and a failed token refresh from bad client credentials no longer evicts the stored token.