AllCrew Figma Workspace MCP Server
Provides a local-first Figma development plugin and MCP workspace that exposes Figma variables, components, prototype edges, assets, and live file state to agents; supports controlled writes to the open Figma file and exports design tokens and project deliverables.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@AllCrew Figma Workspace MCP Serverexport the design tokens from the Figma file I have open"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
AllCrew Figma Workspace
A local-first Figma development plugin and MCP workspace for Claude Code, Codex, Cursor, design tokens, project export, and controlled agent writes.
Download the latest release · Install · Security model · Report an issue
Why
Design handoff is more than pixels. Agents need the variables behind values, component behavior, prototype edges, assets, and the live state of the file a designer has open. AllCrew exposes that context through a user-run loopback bridge, then keeps reads and writes behind separate switches visible in Figma.
It also exports usable deliverables: mode-aware tokens, React, Django, Tauri, native color resources, documentation, and declarative custom plugin screens.
Core extraction and generation run inside Figma. Optional Delivery and Agent Listener connections go only to endpoints the user configures; AllCrew operates no hosted relay and collects no telemetry.
Related MCP server: figma-mcp
AllCrew vs. the official Figma MCP server
The two are complementary, and AllCrew is built to be used alongside Figma's own server rather than instead of it. Figma's remote MCP is hosted, broad and OAuth-based; AllCrew is a development-plugin workspace for explicit, open-file automation under the designer's read/write gates, and for repository-grade artifacts.
Capability | Official Figma MCP | AllCrew Figma Workspace |
Structured components, variables, layout, motion context | Yes | Yes, plus variable-font axes, shaders, vector regions, history |
Generate code from selected frames | Yes, React/Tailwind-shaped by default | Packaged token / Django / React / Tauri / iOS / Android exports |
Write to the canvas | Remote: | Typed, schema-checked ops in the open file, behind Allow changes, one undo step each |
Find library components and styles |
| Imports by published key; uses Figma's search for discovery ( |
Code Connect | Reads/writes mappings ( | Emits Code Connect 2 templates; runs the official CLI ( |
FigJam, Slides, Specs, generative plugins, shaders, Weave | Yes | Not in scope |
Open-file, observable operation log; history, comments, org analytics | Not the primary model | Yes |
Declarative in-workspace screens | Hosted generative plugins (different model) | AllCrew SDK modules |
Hosted service / plan | Remote, OAuth, catalog clients, per-seat rate limits; desktop server is read-only | No AllCrew service; loopback bridge; works on any plan |
AllCrew also relays Figma's desktop Dev Mode MCP server through the bridge (figma.mcp), so an
agent connected only to AllCrew can still call get_design_context or get_metadata when Figma
Desktop runs that server and the seat includes Dev Mode.
Official references: Figma MCP server, tools and limits.
Install from a GitHub Release
Download
allcrew-figma-workspace-v<version>.zipandSHA256SUMSfrom the latest release.Verify the archive:
shasum -a 256 -c SHA256SUMSUnzip it without flattening the directory structure.
In Figma Desktop, open Menu → Plugins → Development → Import plugin from manifest….
Select
manifest.jsonin the extracted folder.Run AllCrew Figma Workspace from Plugins → Development.
Figma's browser app cannot import development plugins. Updating means downloading the new release, replacing the extracted directory, and restarting the plugin.
Build and install from source
npm ci
npm run buildImport this checkout's manifest.json. npm run dev rebuilds on change; npm run package
creates the release archive and refuses to package stale bundles.
Use
Open the Figma file that holds your design-system variables & collections.
Run the plugin. It scans automatically and shows a summary: collections, modes (themes) and variable counts.
Click ⚙Settings (top-right) to open the settings page and tune the output (see below). Choices persist per-user via
figma.clientStorageand re-apply on every run.Download package (.zip) — or grab any single file. Rescan after you edit variables.
Resizable panel
Drag the bottom-right corner to resize the plugin between 340×320 and 1240×1200 pixels.
Double-click the handle to fit the current page's height without changing its width. The size is
stored per machine in figma.clientStorage and restored on the next run. Every native view
consumes the available width; at narrow sizes the header, settings controls, palette editor,
module actions, tables and footer collapse or scroll without clipping. At 560 pixels wide the
palette editor gains a third field column. At 720 pixels the export, token, DS Tools and Agent
Listener screens switch to semantic split grids. Django and Tauri keep setup and review work in
independent vertical lanes, so a tall linter cannot push a wide regeneration card below an empty
grid row. Dense palette, remap and video workflows retain wider spans, while declarative module
blocks use an auto-fit grid. Heterogeneous screens avoid row-spanning cards, so tall skill/code
content cannot create a hole beneath a shorter sibling.
The internal workspace stops growing at 1200 pixels, Agent skill content is capped at 520 pixels,
and lone DS generator cards stop at 560 pixels in split layouts. Resize drag accumulates consecutive screen-space
deltas in floating-point dimensions, clamps that accumulator without overshoot, and rounds only
the size sent to Figma. Reversing direction therefore moves the edge immediately on mouse and
trackpad input. Live requests are coalesced per frame and only the final size is persisted.
The live browser mirror follows its browser window and hides the Figma-only resize handle.
Agent Listener quick start
The release archive includes the exact bridge and MCP front built with the plugin. The downloaded bridge is one self-contained ESM file, including every local module it needs:
node tools/bridge.mjsOpen Agent Listener in the plugin and pair during the five-minute window. Allow reads and Allow writes control external agents only. Independently, Browser UI mirror publishes the live panel through the loopback bridge; the browser icon in the header opens the exact connected file in Chrome.
Agent calls use one authenticated loopback WebSocket per open Figma file and fall back to HTTP
polling when a previous bridge does not advertise socket support. The bridge also reads Figma
Desktop's local tab list to resolve file keys without asking for URLs; history.recent summarizes
version checkpoints across files opened on this machine.
The agent channel publishes a fidelity-first product-design contract through both the generated
skill and MCP initialization. Before choice-bearing UI work, agents check repository context and
call design.evidence; existing-screen reproduction proceeds without a broad brief, while a new
surface missing its product goal, primary user/job, or visual direction produces one grouped
clarification question. Platform standards fill accessibility and system-behavior gaps only, never
brand styling. For an MCP client:
node tools/mcp.mjs --install claude
# cursor, windsurf and vscode are also supportedGrants are remembered per file. A development plugin gets no fileKey, so the first grant in a file stamps it with a random id in the document's plugin data. Reopening or reloading the plugin in that file restores the listener with the same read and write switches. Renaming the file keeps the grant. A different file, even one with the same name, starts with both gates closed. A duplicate of the file keeps the stamp, and with it the grant.
What you get
File | Contents |
| CSS custom properties. One self-contained block per theme. The merged single file. |
| The same blocks split one CSS Module per theme ( |
| Canonical W3C token tree — every mode under |
| Typed |
| (iOS & Android tokens on) One colorset per colour, in sRGB components, with a |
| (iOS & Android tokens on) The same, by Android resource qualifier. The night file is an override list: it carries only the colours that differ. |
| (iOS & Android tokens on) Constants for what a catalogue cannot hold (spacing, radii, the type scale) and for the colours too, for a build that themes in code. Values identical in every theme are declared once, above the per-theme blocks; a length gets |
| Auto-generated usage notes for that specific export. |
Settings
The Export Settings page (open it from the gear in the header) isolates the opinionated parts of the output so different consumers can keep their own preferences (the choices persist per-user):
Setting | Default | What it does |
Inline primitives | on | Resolves aliases that point at a primitive (raw, single-mode value) into the literal, and drops the primitive layer. Aliases between semantic tokens stay as |
Flatten all aliases | off | With Inline primitives on, also resolves the semantic→semantic |
Theme attribute |
| The attribute the theme blocks key off ( |
Theme collections table | empty | Maps any number of free-plan single-mode collections into exported multi-theme structures. Each row names the output structure, source collection and exported theme; repeat a structure name for Light, Dark, High contrast or additional brand themes. Conventional |
Per-theme | on | Whether to emit the per-theme module files alongside |
CSS Modules | on | Wrap module-file selectors in |
iOS & Android tokens | off | Also emit the asset catalogue, |
Collections in the export | Export | One row per variable collection of the open file, with a role picked from a list: Export, Hide from code (a primitive layer stays out while its values still reach the tokens that alias it), Breakpoints → |
Include library variables | off | Also export the enabled libraries' own collections. Not needed for values this file takes from a library: aliases into a library are resolved by id and substituted without it. It is a network read per token, and the panel reports progress while it runs. |
Automatic update discovery | off | Checks the latest GitHub Release at most once every 24 hours. A manual Check now action is always available. When a newer version exists, the plugin shows a quiet header banner with release and direct-download actions. |
Browser UI mirror | off | Publishes the exact live plugin panel through the paired loopback bridge, independently from Agent Listener read/write permissions. While enabled, the header browser icon opens the connected file in Chrome. |
tokens.css shape
Themes are the modes of collections that have more than one. With none, the
stylesheet is a single :root. The default theme occupies :root, so an
un-themed page still renders; every other theme block holds only the values it
changes.
Before anything is emitted the export is shaped for code:
collections reached only through an alias, and collections marked Hide from code, are substituted into the tokens that use them and dropped;
variables whose name has a group or segment starting with
_or.(Figma's spelling for "not for consumers") are left out, while anything aliasing them still resolves;--bp-*come only from threshold variables (breakpoint/<name>,bp/…,min-width/…), narrowest first — never from frame widths;TIMINGvariables export as milliseconds,EASINGvariables as theircubic-bezier()(or a sampledlinear()for a spring), and a composed colour ({ color, opacity }) as the flat colour it paints in each mode;opacity tokens, which Figma holds as percentages, export as 0–1 fractions;
colours authored outside sRGB keep their space:
oklch(),oklab()orcolor(display-p3 …). Figma itself drops the authored space from a solid fill or stroke whose opacity is below 100% (gradient stops keep it), so such a paint exports as its sRGB projection.
With Inline primitives on (the default), the raw scale is collapsed into the tokens that use it — no primitive variables in the output:
:root,
[data-theme-name="Light"] {
/* Tokens */
--colors-action-default: #002780;
--spacing-md: 8px;
}
/* add a "Dark" mode to the collection in Figma → a second block appears */
[data-theme-name="Dark"] {
/* …only the variables whose dark value differs… */
}Turn it off to keep the primitive layer and reference it via aliases instead (byte-identical to the AllCrew Figma Workspace board, modulo the attribute name):
:root,
[data-theme-name="Light"] {
/* Primitives */
--colors-b-800: #002780;
--sizes-1: 8px;
/* Tokens */
--colors-action-default: var(--colors-b-800);
--spacing-md: var(--sizes-1);
}A primitive used directly in code (not via an alias) can't be detected by the plugin — it will be inlined away. Turn Inline primitives off if you rely on the raw scale at call sites.
Switch theme by setting the attribute on any ancestor:
<html data-theme-name="Dark">Per-theme <theme>.module.css
The same theme blocks are also emitted one file per theme, named after the mode
(light.module.css, dark.module.css, … — slugified; default.module.css when
the export is a single :root), wrapped in :global(…) so they're valid in a
CSS-Modules project (Next.js, etc.). A collection marked Separate file gets
its own <name>.css, and its modules are prefixed with it
(business-dark.module.css); the export summary names the tokens one product
file defines that another lacks.
/* light.module.css */
:global(:root),
:global([data-theme-name="Light"]) {
--colors-action-default: var(--colors-b-800);
--spacing-md: var(--sizes-1);
}The default theme also lands on :global(:root). Concatenating every
*.module.css (or letting your bundler merge the ones you import) reproduces
tokens.css — that's the "merged single file". For runtime theme-switching,
keep every theme present (use tokens.css, or import all *.module.css) and
toggle data-theme-name.
Django app export
With the Django × Bootstrap preset the package is not a folder of templates waiting for someone to write a URLconf — it is a site you can run:
unzip export.zip -d site && cd site
pip install -r requirements.txt
DJANGO_DEBUG=1 python manage.py runserverWhat ships | Where it comes from |
| Settings → Target → Django project (off when you apply onto a project that already has its own) |
| the frames in scope; a component set becomes one partial, its variants CSS modifiers |
| layout/paint, design tokens, reaction states, prototype page transitions |
| Settings → i18n → Target languages |
design/pages.py is the generated page registry — the one file a re-export
overwrites. Everything else reads it: the prototype's start frame serves at /,
every other page at /<slug>/, and the navMap context processor reverses those
routes so the {{ navMap.nav_… }} hrefs the markup already emits resolve (and pick
up the active locale prefix). Configure Target languages and the routes move
under i18n_patterns with a no-JS language switcher in the shell.
Markup. A frame named Header/Nav/Footer/Sidebar becomes that element, a page root's
other direct children become <section>, a text layer whose name states a level (Heading 2,
Title/H3) becomes <h2>/<h3> — with exactly one <h1> per page — and a layer that only reacts
to a click becomes a real <button type="button"> instead of a div no keyboard can reach. Figma's
own auto-names (Rectangle 12, Vector) render as alt=""/aria-hidden, because a screen reader
announcing "Rectangle 12" is worse than silence. A layer the designer pinned with Fixed position
when scrolling gets position: fixed/sticky — that choice used to be read and then dropped.
What the CSS pass could only approximate or could not draw at all (squircle corners, NOISE /
TEXTURE / GLASS / SHADER effects and fills) is listed per node in export-report.json under
fidelityNotes, instead of disappearing into a console.
Motion. Reaction states (ON_HOVER/ON_PRESS/ON_CLICK → CHANGE_TO) are
diffed against the destination variant including its descendants, so a hover that
recolours an icon, re-pads a button or reveals a badge survives. Overlays render the
destination frame's own markup into a <dialog> with an open/close animation.
Page-to-page prototype transitions (DISSOLVE / PUSH / MOVE / SLIDE / SMART_ANIMATE)
become cross-document view transitions — Smart Animate assigns a shared
view-transition-name to the layers matched across both screens. Everything is
wrapped in prefers-reduced-motion guards.
The Smart Animate diff behind those states covers what Figma covers: position, size, rotation,
opacity, corner radii, strokes, the whole fill stack (gradients included, marked non-interpolable
where CSS cannot tween them), shadows and blurs, text size/weight/spacing/colour, auto-layout
padding and gap, visibility (with transition-behavior: allow-discrete) and blend mode — on the
changed layer itself as well as its descendants. A state that swaps a photo ships the
destination variant's image with the page assets and references it; when that image cannot be
exported the declaration is dropped rather than emitted as the url(<path-to-image>) placeholder
Figma hands out, which resolves to a 404 and blanks the element on hover.
Springs. A CUSTOM_SPRING reaction is solved from its own physics (ω₀ = √(k/m),
ζ = c / 2√(km), plus the designer's initialVelocity), and a named preset (GENTLE, BOUNCY, …)
from its bounce at the frequency whose period is the duration typed next to it. The emitted
linear() curve runs for the spring's real settle time, so a 150 ms bouncy press is a 150 ms-ish
bounce rather than the same half-second curve every spring used to collapse into.
Figma publishes no physics for its named presets, so those bounces come from a reverse-engineered
table — the one estimate left in the emitted motion, and export-report.json says so:
estimatedSpringPresets counts the curves that rest on it, and springPresetCalibration reports
what the file's own springs say the table should hold whenever Figma does supply the numbers. A
preset that arrives with real parameters is solved from them and never touches the table at all.
Adaptivity. Frames named Home / desktop + Home / mobile collapse into one
page: the widest frame is the DOM, the narrower ones become @media blocks, and
nodes that exist only in a narrow frame (the burger, a stacked CTA) are spliced in
hidden and revealed at their breakpoint. base.html carries the viewport meta, so
those queries apply on a real phone.
React export & Code Connect
The React target writes a component per Figma component set, and beside each one a
<Component>.figma.ts — a Code Connect template that tells Figma this is the code for that
design component. From then on Dev Mode and Figma's official MCP answer with
<Button type="Primary" …> and a real import, instead of anonymous markup a developer has to
recognise. Code Connect 2 accepts only template files (the figma.connect(...) parser files of
1.x are refused by its CLI), and the export writes nothing else:
a parserless
figma.config.json(include: src/components/**/*.figma.ts, labelReact, languagejsx);@figma/code-connect^2.0.2 in devDependencies, withnpm run figma:check(templates typed against Code Connect's ownfigma-typesintsconfig.figma.json, kept out of the app build) andnpm run figma:publish.
Props are not guessed: the same variant/text/boolean definitions the component's own union types
come from are read with getEnum / getString / getBoolean on figma.selectedInstance, with
the Figma property names verbatim — Button-lable and Meduim are mapped as spelled, because that
is what the file contains — and rendered through figma.helpers.react.renderProp. Each template's
id is derived from the node id, so renaming a component or re-exporting keeps the published
mapping.
The file key. A template addresses its component by node URL, and the URL needs the file's
key, which only a private Organization plugin can read (figma.fileKey). Everywhere else pass the
file's Figma link as fileUrl to EMIT_REACT; through the bridge it is filled in automatically
from Figma Desktop's open tab when the bridge can match one. Without a key no templates are
written at all and the export report says how to provide one — a mapping pointing at a guessed
key would map nothing.
Publishing from an agent. The bridge runs the pinned official CLI on the repository
directory — codeconnect.parse and codeconnect.map offline, codeconnect.preview and
codeconnect.publish (a dry run unless dryRun: false, which needs the plugin's write gate) with
a token. See the Code Connect ops in agent/README.md.
Two things worth knowing before troubleshooting a publish:
The library does not have to be published. A mapping addresses its component by node URL (
?node-id=819-95512— a dash, not the colon Figma stores), sopublishStatus: UNPUBLISHEDpublishes fine. Publishing only matters for the MCP route, whereadd_code_connect_maprefuses with "Published component not found".The token. A personal access token with the Code Connect: Write and File content: Read scopes works, Professional plan included (one library and one framework there); on Organization/Enterprise a plan token of the Figma CLI category works too. A REST-category plan token cannot publish. The CLI reads
FIGMA_ACCESS_TOKEN; the bridge passes it the same token it uses for REST ($FIGMA_TOKEN, else~/.allcrew-channel/figma-token).
Design-to-site fidelity check
npm run fidelity compares Figma frames with the live (or local) pages built from them, matching
by text rather than by markup: wrong type styles, displaced or re-wrapped text, sections that
push everything below them, design text baked into images, and content missing from the page.
It reads the design over REST (cached per file version) and shoots the page in system Chrome, so
Figma does not need to be open. npm run fidelity:deliver turns a run into ticket drafts that
carry the design's own spec, Figma comments pinned per section, and a CSS fix brief. Setup,
findings and the project config format: docs/fidelity.md.
Automated delivery
Reading variable values over REST is Enterprise-only, so on Organization the plugin itself is the extractor — but everything around it is automated. The designer publishing the library is the trigger; the plugin ships the package; a small receiver does the git/npm/folder work.
Plugin side (Export Settings → Delivery):
Download receiver.mjs — the script ships inside the plugin, because a designer who installed it from Figma has no checkout of this repo. It is injected at build time from
server/receiver.mjs, so the copy they get can never be a different version.Receiver endpoint — where to POST (configurable; nothing is pinned to one host).
Shared secret — sent as
x-allcrew-channel-secret; must match the receiver. (This is the only secret in the plugin — git/npm credentials never leave the receiver.) Pair fills both fields from a receiver running on this machine; see Secrets below.Target —
folder/git(commit + push) /pr(branch +gh pr create) /mr(branch + GitLab merge request) /npm(publish), plus the non-secret route (repo / branch / base / project / path / package). Only the fields the chosen target uses are shown.Dry run (on by default) — the receiver prepares the change in its work dir and answers with a per-file plan (create / update / unchanged, lines added and removed, binary files flagged, the diff) without writing, committing or pushing. Preview always asks for a dry run; Deliver now follows the switch. A PR or MR result carries its link.
Send after export — every Django or token export is delivered once it is built.
Receiver side (server/receiver.mjs) — a dependency-free Node script you run on any
host; it executes the target using the host's own git / gh / npm auth:
ALLCREW_CHANNEL_FOLDER_BASE=/abs/path/for/folder/target \
node receiver.mjs # listens on :8787 (override with PORT)Secrets
Nothing is baked into the build and nothing is distributed. A receiver with no
ALLCREW_CHANNEL_SECRET mints its own into ~/.allcrew-channel/receiver-secret (0600) and opens a
five-minute pairing window; the plugin's Pair button collects it, and the first pair
closes the window. Ten designers means ten different secrets, none of which anyone had to
send anyone. Rotate by deleting the file and restarting.
Pairing is unauthenticated by design, bounded three ways: loopback only, five minutes from a start someone typed by hand, and closed by the first success. It grants nothing a local process could not get by reading the same file.
For a shared or remote host, set ALLCREW_CHANNEL_SECRET yourself — the receiver uses it and opens
no pairing window (--pair forces one), and you type the same value into the plugin.
Never bake one secret into the plugin for everyone: it is a key to every teammate's host
that cannot be rotated without a rebuild.
The plugin only ever HTTP-POSTs, so the receiver is portable: point the plugin's endpoint
at wherever it runs (localhost, a Tailscale host, CI). The contract is one JSON POST of
{ target, dryRun, route, files }, where each file is UTF-8 text or { "base64": "…" }
for bytes; the receiver verifies the secret, checks every path and value before writing
anything, runs the target and returns { ok, target, dryRun, detail, plan, url }. See
server/README.md for the full contract and the branch guards (git
refuses the default branch; pr/mr refuse an existing, protected or base branch).
Fully hands-off (no designer either) would need the Variables REST API = Enterprise. The transform is already shareable, so that upgrade is a small step if you ever take it.
Deployment & Integration
Prerequisites
The receiver uses only Node.js built-ins — no npm install. The host needs:
Target | Required on the host |
| Node.js only |
|
|
|
|
|
|
|
|
Environment variables
Variable | Required | Default | Description |
| no | minted | Set it to manage the secret by hand; suppresses pairing |
| no |
| Where a minted secret is stored |
| no |
| HTTP port |
| for | — | Absolute base dir; |
| no |
| Scratch dir for git clones and dry-run plans |
| no |
| Largest accepted delivery body |
| for | — | GitLab token used to open merge requests and read protected-branch rules |
| no |
| GitLab API root for self-hosted instances |
| no | AllCrew defaults | Commit identity |
| no | off |
|
Option 1: developer machine (Tailscale)
The simplest setup — run the receiver locally, expose it to other Figma sessions via Tailscale:
# .env (keep out of git)
ALLCREW_CHANNEL_SECRET=some-random-string
ALLCREW_CHANNEL_FOLDER_BASE=/Users/you/projects/design-tokens/src
# run
ALLCREW_CHANNEL_SECRET=some-random-string \
ALLCREW_CHANNEL_FOLDER_BASE=/Users/you/projects/design-tokens/src \
node server/receiver.mjsPlugin endpoint: http://<tailscale-hostname>:8787
Option 2: long-running server with pm2
npm install -g pm2
# create ecosystem file — .cjs, since this repo is "type": "module"
# (do NOT commit — contains the secret)
cat > ecosystem.config.cjs <<'EOF'
module.exports = {
apps: [{
name: "allcrew-channel-receiver",
script: "server/receiver.mjs",
env: {
ALLCREW_CHANNEL_SECRET: "your-secret-here",
ALLCREW_CHANNEL_FOLDER_BASE: "/srv/tokens",
PORT: "8787"
}
}]
}
EOF
pm2 start ecosystem.config.cjs
pm2 save && pm2 startup # survive rebootsOption 3: systemd service
# /etc/systemd/system/allcrew-channel-receiver.service
[Unit]
Description=AllCrew Figma Workspace receiver
After=network.target
[Service]
ExecStart=/usr/bin/node /opt/allcrew-channel/server/receiver.mjs
Restart=on-failure
Environment=PORT=8787
Environment=ALLCREW_CHANNEL_SECRET=your-secret-here
Environment=ALLCREW_CHANNEL_FOLDER_BASE=/srv/tokens
WorkingDirectory=/opt/allcrew-channel
[Install]
WantedBy=multi-user.targetsystemctl enable --now allcrew-channel-receiverOption 4: Docker
FROM node:20-alpine
RUN apk add --no-cache git github-cli npm
WORKDIR /app
COPY server/receiver.mjs ./
EXPOSE 8787
CMD ["node", "receiver.mjs"]docker build -t allcrew-channel-receiver .
docker run -d \
-p 8787:8787 \
-e ALLCREW_CHANNEL_SECRET=your-secret \
-e ALLCREW_CHANNEL_FOLDER_BASE=/tokens \
-v /host/tokens:/tokens \
allcrew-channel-receiverOption 5: GitHub Actions (webhook-style)
The receiver itself is a push-side HTTP server, not a pull-side CI job. But you can use the git or pr target and let the receiver commit into the repo — Actions will then pick it up on push.
Designer triggers delivery in Figma
→ receiver commits tokens to `tokens` branch
→ Actions workflow runs on push to that branch
→ (optional) opens PR into main, runs style-dictionary, etc.)Example Actions workflow for the downstream step:
name: Sync design tokens
on:
push:
branches: [tokens]
paths: ["tokens/**"]
jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npx style-dictionary build # or whatever your pipeline is
- run: |
git config user.name "tokens-bot"
git config user.email "tokens@allcrew-channel.local"
git add -A && git diff --cached --quiet || git commit -m "chore: rebuild tokens"
git pushCredentials
The receiver inherits credentials from the host environment — never from the plugin or the POST body.
Target | How to auth |
| SSH key in |
|
|
|
|
Health check
curl http://localhost:8787
# {"ok":true,"service":"allcrew-channel-tokens-receiver","targets":["folder","git","pr","mr","npm"],"capabilities":{…}}Troubleshooting
Symptom | Likely cause |
| Secret in plugin doesn't match |
| Host's git auth not set up for that remote |
|
|
| Env var not set |
No changes committed | Tokens were already up-to-date (not an error) |
Notes
Themes = the modes of your semantic (alias-bearing) collection. A raw primitives collection with a single placeholder mode (e.g.
Mode 1) is not treated as a theme; its values fold into every theme block.Token extraction is offline. Reading variables, building the package and writing every artifact happen entirely in-editor; fonts (Museo Sans, Geist Mono) are embedded in
ui.html.manifest.jsondoes declarenetworkAccess.allowedDomains: ["*"], and it has to: two optional features dial out and neither host can be known ahead of time — the delivery step POSTs the package to a receiver the user configures, and the agent listener talks to a loopback bridge on a port the designer chooses.allowedDomainstakes whole URLs and a port cannot be wildcarded, so a narrower list would be wrong for half of any team. Nothing in the extraction path uses it.Editors & plan: runs in both Figma Design and Dev Mode (
editorType: ["figma", "dev"],capabilities: ["inspect"]). It reads variables through the in-editor Plugin API, so no Enterprise / Variables REST API is required — it works on any plan, including Organization. Dev Mode itself is gated by Figma to a Dev or Full seat (no manifest setting bypasses that); anyone with a normal editor seat can still run it in Design mode with no extra cost.Selector: defaults to
[data-theme-name="…"](per spec) but is configurable in Export settings — set it todata-themeto match the AllCrew Figma Workspace board.tokens.jsonis always the full, un-inlined tree regardless of the Inline primitives setting, so it stays lossless for diffing / re-import.
Agent Listener security
The bridge listens on
127.0.0.1by default. Do not expose it on a public interface.Read and write access are separate switches, and both start off.
Figma development plugins do not expose a stable file key. Agent grants therefore last only for the current plugin session and are never restored by file name.
Treat
~/.allcrew-channel/agent-secret, receiver secrets, and Figma tokens as credentials.Stop the listener before opening an untrusted file. Enable writes only while an intended automation is running.
See SECURITY.md for the trust model and private vulnerability reporting.
Support
Data handling: PRIVACY.md.
Bugs and feature requests: https://github.com/mal4i6ka/allcrew-figma-plugin/issues
Security reports: https://github.com/mal4i6ka/allcrew-figma-plugin/security/advisories/new
License
MIT © 2026 Alexander Lugachev.
Maintenance
The transform in code.js is a dependency-free port of the AllCrew Figma Workspace board's
src/lib/figma.ts (variablesToW3CMultiMode) + src/lib/design-system/tokens-transform.ts.
With Inline primitives off and the theme attribute set to data-theme, the
output is byte-identical to the board. The export options layer on top of that core
transform; keep the core in sync with the board if the token format changes.
Files:
manifest.json plugin manifest
dist/code.js sandbox and Codegen runtime
dist/ui.html plugin panel
agent/ local bridge, MCP front, and channel documentation
server/ optional Delivery receiverThis server cannot be deployed
Maintenance
Related MCP Connectors
Serves your design system and coding standards to coding agents, so they stop guessing.
Live React design-system APIs, patterns, and code validation so AI agents build real UI, not slop.
Connect AI coding agents to Anima Playground, Figma, and your design system.
Access and maintain design system docs, tokens, components, skills, and contexts across any project.
Related MCP Servers
- AlicenseNot gradedqualityBmaintenanceEnables AI coding agents to read and write a user's Figma file through the Figma Plugin API, offline and privately, without API tokens or rate limits.257 npmMIT
- AlicenseNot gradedqualityAmaintenanceEnables AI coding assistants to draw UI directly on a Figma Desktop canvas and read existing designs back as structured JSON, tokens, CSS, and screenshots, all over a localhost bridge without external API keys.85 npm2MIT
- AlicenseNot gradedqualityAmaintenanceEnables AI agents to inspect Figma selections, navigate pages, render previews, generate starter code, and submit user-approved canvas edits through a local bridge.3 npmMIT
- AlicenseNot gradedqualityAmaintenanceEnables AI agents to read, create, edit, and organize Figma documents directly through a local plugin bridge, without needing a Figma AI subscription.3Apache 2.0