tokensStudioMCP
# ft — Tokens Studio applied tokens, one command
`ft` reads the design tokens applied by the
[Tokens Studio for Figma](https://www.figma.com/community/plugin/843461159747178978/tokens-studio-for-figma)
plugin and prints them next to the layers that use them. Paste a Figma
URL into your terminal and you get back an annotated layer tree, a
grouped token dictionary, or a style-gap report — whichever one you
asked for.
```bash
# Copy a Figma frame URL in your browser, then:
ft
```
That's it. No quoting, no setup beyond a one-time Figma token, no
Figma desktop app. `ft` also runs as an **MCP stdio server** so Claude
Code can read applied tokens without leaving the chat.
---
## Why this exists
Figma's official Dev Mode MCP server exposes layer metadata (ids,
names, types, coordinates) but **not** the Tokens Studio data —
because that data lives in `sharedPluginData` on every node, under
the `tokens` namespace, and is invisible to most REST consumers.
The result: an LLM code agent can see the layers but not which
tokens drive which properties, so generated code falls back to
hard-coded colours and spacings.
`ft` closes that gap. One REST call with `plugin_data=shared`, one
walk of the returned tree, and every node comes back labelled with
its Tokens Studio tokens — ready for the next `ft tokens` or for
Claude Code to consume over MCP.
---
## Install
```bash
git clone https://github.com/Blyawon/tokensStudioMCP.git
cd tokensStudioMCP
npm run setup
source ~/.zshrc # or restart your terminal
```
`npm run setup` runs the whole chain:
1. `npm install`
2. `npm run build` — compiles TypeScript to `dist/`.
3. `npm run alias` — installs `ft` and `figtokens` aliases in
`~/.zshrc` (or `~/.bashrc`). On zsh they're wrapped in `noglob`
so `?` in URLs doesn't trigger globbing.
4. `node dist/index.js setup` — prompts for your Figma personal
access token and saves it to `.env` (`chmod 600`).
**Get a token** at <https://www.figma.com/developers/api#access-tokens>
with scope **File content: Read-only**. The setup step links you
there and walks you through it.
Requires **Node.js ≥ 18** (native `fetch`).
---
## Quick start
The fastest path is clipboard mode — no quoting, no shell gotchas:
```bash
# 1. Copy any Figma frame URL in your browser.
# 2. Run:
ft
```
With no arguments, `ft` reads the URL from your clipboard
(macOS `pbpaste`). You can also pass a URL directly:
```bash
ft 'https://www.figma.com/design/abc/File?node-id=1-2'
```
### Sample output
```
resultpage_lg COMPONENT 2007:102481 coverage=1735/2903
└─ .appShell INSTANCE 94:774 fill=page.background.100
├─ .navigation INSTANCE 93:3974
│ └─ .collapseButton INSTANCE 20:814 sizing=dimension.2xl
│ └─ buttonAction INSTANCE 19:792 composition=…
└─ .sectionList INSTANCE 101:222718
└─ items SLOT 101:214831 itemSpacing=section.spacing.prominent.md
├─ (×4) container INSTANCE 102:269769 composition=…
└─ footer INSTANCE 102:269770 fill=colors.surface.default
```
- One line per node: `<name> <TYPE> <id> <tokens…>`.
- Adjacent siblings with identical structure + tokens collapse into
`(×N)`.
- Untokenized nodes show no trailing token cluster — absence is the
default.
- The root carries `coverage=<with>/<total>` so you see how tokenized
the selection is at a glance.
- `composition=…` marks nodes that use a composition token; see
[Composition tokens](#composition-tokens) below.
---
## Commands
```bash
ft # clipboard URL → compact tree (same as `ft <url>`)
ft <url> # compact tree of a frame with applied tokens
ft tree <url> # same as `ft <url>` (explicit)
ft tokens <url> # grouped token dictionary + style-gap report
ft coverage <url> # % of nodes that have tokens, with a progress bar
ft node <url> # tokens applied to one node
ft config # show the effective config and where it came from
ft setup # save or replace your Figma access token
ft help # cheat sheet with every flag
ft mcp # run as an MCP stdio server (Claude Code uses this)
```
### `ft tokens` — the cheap pre-flight
Ask "which tokens does this frame actually use?" before fetching
the full tree. Output is grouped by property key (`fill`, `spacing`,
`typography`, `composition`, …), values sorted alphabetically, each
value annotated with the layer names that use it.
```
47 unique tokens across 8 properties
fill (3)
colors.border.subtle used by: .divider ×4, .card ×2
colors.text.primary used by: .title, .body ×6
page.background.100 used by: .appShell
spacing (5)
section.spacing.prominent.md
spacing.lg
spacing.sm
…
composition (27)
ecommerce.container.base.size:lg
styles.buttonAction.base.variant:control.size:sm.hover
…
▸ 12 nodes have visual styling with no covering token
```
The trailing style-gap line is a count of nodes that have visual
styling (fills, strokes, effects, shared styles) but no Tokens
Studio token covering that property. `--no-warn` silences it.
### `ft coverage` — fast sanity check
```
[█████████████░░░░░░░] 1735 / 2903 (60%)
```
Use it to sanity-check whether a file is tokenized at all before
you start processing anything. Prints a plain text line instead of
the bar when stdout isn't a TTY.
### `ft node` — one-node snippet
```bash
ft node 'https://www.figma.com/design/abc/File?node-id=1-2'
```
Returns a single-node XML snippet with just the `<tokens …/>` child.
Useful when you already know the node id and want the smallest
possible answer.
---
## Flags
Every CLI command accepts the same flag set. Grouped by intent:
### What to show
| Flag | What |
|---|---|
| `-o, --only-with-tokens` | Hide branches that contain no tokens anywhere |
| `--all-layers` | Show every layer, even untokenized ones (overrides config) |
| `-g, --gaps` | Hide branches that contain no style gaps |
| `--with-components` | Include `COMPONENT` / `COMPONENT_SET` nodes (hidden by default) |
| `--with-vectors` | Include vector nodes that have no fill (hidden by default) |
| `--with-composition` | Show composition tokens inline instead of the `…` placeholder |
| `--no-warn` | Don't flag untokenized visual styling |
| `--all` | Turn off every filter for this run |
### How to show it
| Flag | What |
|---|---|
| `-d, --depth N` | Cap subtree depth |
| `-n, --node 1:2` | Supply a node id when the URL doesn't have one |
| `--layout` | Append `[x,y w×h]` to each line |
| `--xml` | Emit legacy Figma-MCP-style XML instead of the compact tree |
| `--json` | Emit a structured JSON object on stdout (tree, tokens, coverage, node) |
| `--no-dedupe` | Don't collapse repeated sibling groups |
Example:
```bash
ft 'https://www.figma.com/design/abc/File?node-id=1-2' --depth 3 -o
```
### `--json` output
Every command that returns data (`ft`, `ft tree`, `ft tokens`, `ft
coverage`, `ft node`) accepts `--json`. The object always has a
`format` discriminator so one consumer can tell the shapes apart.
```bash
ft tokens 'https://www.figma.com/design/abc/File?node-id=1-2' --json
```
```json
{
"format": "tokens",
"totalUnique": 47,
"totalProperties": 8,
"compositionHidden": 27,
"properties": {
"fill": {
"colors.brand.primary": [
{ "name": "button", "type": "INSTANCE", "count": 4 },
{ "name": "link", "type": "TEXT", "count": 2 }
]
}
},
"gaps": [
{ "name": "divider", "type": "LINE", "id": "1:27", "gaps": ["borderColor"] }
]
}
```
Tree JSON carries a `coverage` object and a nested `root` with
`{ id, name, type, tokens?, gaps?, characters?, layout?, children? }`
on every node. Coverage JSON is a plain
`{ format: "coverage", withTokens, total, percent }`. Node JSON is a
single-node snapshot with the display tokens inlined. None of them
print the splash or summary divider — stdout stays clean for piping
into `jq`, other scripts, or downstream codegen.
---
## Composition tokens
Tokens Studio lets you apply a single **composition token** to a
node that bundles multiple property styles at once (e.g.
`button.primary.hover` → fill + border + padding + typography).
That's great for design maintenance but terrible for automatic
codegen — a composition token's value is an opaque string.
`ft` handles composition tokens this way:
- **Coverage counts them.** A node with only a composition token is
counted as tokenized. It does **not** show up as a gap.
- **Display strips them by default.** The compact tree shows
`composition=…` as a placeholder so you know one is present
without drowning the output in long composition paths. Pass
`--with-composition` (or `includeComposition: true` in config,
or the MCP tool parameter) to see the full value.
- **Gap detection trusts them.** Because a composition token can
cover fill/stroke/spacing/typography all at once, nodes with a
composition token applied never report style gaps. This is the
right default for the common Tokens Studio workflow.
`ft tokens` surfaces a one-line note when composition tokens are
present, so you're never guessing why a visually-styled frame looks
"empty".
---
## Config file
Put persistent defaults in `~/.ftrc.json` (global) or
`./ft.config.json` (per-project). Any key is optional.
```json
{
"ignoreVectorsWithoutFill": true,
"ignoreComponents": true,
"warnStyleGaps": true,
"onlyWithTokens": false,
"includeComposition": false
}
```
Project config wins over global config; CLI flags win over both.
`ft config` prints the effective config and shows which file each
value came from.
`--all` bypasses the config entirely for one run — handy when you
want to see everything, once, without editing a file.
---
## Shell quoting (zsh + bash)
Figma URLs contain `?` and `&`, both of which are shell
metacharacters:
- **zsh**: `?` triggers filename globbing, `&` triggers job control.
- **bash**: same story for `&`; `?` is usually safe unless `failglob`
is set.
`npm run setup` installs the `ft` alias wrapped in `noglob` on zsh,
so **bare `?` is safe** even without quotes. `&` still splits the
command line (job control is not part of filename expansion and
can't be disabled by `noglob`), so URLs containing `&` still need
single quotes.
```bash
# zsh:
ft https://www.figma.com/design/abc/File?node-id=1-2 # ok (noglob)
ft 'https://www.figma.com/design/abc/File?node-id=1-2&t=xyz' # ok (single-quoted)
# bash:
ft 'https://www.figma.com/design/abc/File?node-id=1-2' # always single-quote
```
**The easy way to sidestep all of this:** copy the URL in your
browser and just run `ft`.
`ft` detects the classic "zsh ate my URL" pattern (a Figma URL with
query params but no `node-id`) and prints a soft warning to stderr
telling you to either single-quote the URL or use clipboard mode —
no silent failures.
---
## Use it from Claude Code
```bash
claude mcp add tokens-studio node "$PWD/dist/index.js"
```
(No subcommand — `node dist/index.js` with no args and a non-TTY
stdin runs the MCP server.)
Three tools are exposed:
| Tool | What it does |
|---|---|
| **`list_tokens`** | **START HERE.** Unique tokens grouped by property, with layer usage and a style-gap report. Cheap pre-flight — call this first to decide whether you actually need the full tree. |
| **`get_metadata_with_tokens`** | Figma-MCP-style XML tree decorated with applied tokens on every node. Instance-path ids collapsed, `hash`/`version` noise stripped, `x/y/w/h` off by default (pass `layout: true` if you need them). |
| **`get_node_tokens`** | Tokens for a single node as a tiny XML snippet. |
All three accept any combination of `url`, `fileKey`, and `nodeId`,
so you can point them at a whole file or a specific frame. All three
respect your config file and the `includeComposition` parameter.
In any chat, ask:
> Use tokens-studio to list the tokens applied in
> `<paste figma url>`, then show me the frame tree only for the
> components that use `colors.brand.primary`.
Claude Code will call `list_tokens` first, see what's there, then
call `get_metadata_with_tokens` with the right filters.
---
## How it works
- Figma's REST API supports `?plugin_data=shared`, which returns
every node's `sharedPluginData`.
- Tokens Studio stores applied tokens under the `tokens` namespace
on each node, keyed by the property they target (`fill`,
`borderRadius`, `spacing`, `typography`, `composition`, …).
- `ft` walks the returned tree and renders it either as a compact
ASCII tree (default) or a Figma-MCP-style XML tree (`--xml`).
- Dedupe is content-hash based: the hash mixes every descendant's
`type + name + tokens signature + recursive child hash`. Two
instances that differ only by a leaf-level token override hash
differently and are kept separate.
- No Figma desktop app needed. Headless. Your token stays in `.env`
on your machine.
---
## Project layout
```
src/
├── index.ts # CLI router + MCP stdio server + tool definitions
├── cli-ui.ts # Spinner, splash, progress bar, colour helpers (TTY-gated)
├── figma-client.ts # Minimal REST client with plugin_data=shared
├── parse-url.ts # Figma URL → { fileKey, nodeId? }
├── tokens.ts # extractTokens / extractDisplayTokens / style-gap logic
├── xml.ts # Legacy XML renderer (get_metadata_with_tokens)
├── render-tree.ts # Compact ASCII tree renderer + token dictionary
├── config.ts # ~/.ftrc.json + ./ft.config.json loader
├── tokens.test.ts # Node test runner suite
└── render-tree.test.ts
```
Run the tests with:
```bash
npx tsx --test src/tokens.test.ts src/render-tree.test.ts
```
---
## Scope
- **Reads** via the Figma REST API. **Writes** are opt-in and go
through the companion plugin (see *Token remapping* below) — the CLI
itself only reads.
- Returns token **names** (reference paths like
`colors.primary.500`) — not resolved values. Composition token
values are shown as full reference paths when
`--with-composition` is on.
- Node 18+ (native `fetch`).
---
## Token remapping (MCP)
When connected via MCP, three additional tools enable AI-driven token
remapping — useful for porting an old component onto a new token set:
- **`propose_token_remap`** — read-only. Takes a Figma URL plus the new
token JSON you pasted in chat (Tokens Studio export, DTCG, or a flat
list of paths — all accepted) and returns a candidate plan with
scores and ambiguous cases for the agent to resolve.
- **`apply_token_remap`** — applies a plan to the live Figma file via
the companion plugin. Whole batch is wrapped in a single Figma undo
entry (Cmd-Z reverts it all). Supports `dryRun: true`.
- **`bridge_status`** — diagnostic; reports whether the WebSocket
bridge is up and whether the plugin is connected.
### Plugin install
The Figma REST API can read shared plugin data but cannot write it —
that mutation is plugin-only. So `apply_token_remap` ships its writes
through a tiny companion plugin you install once:
1. `npm run build:plugin`
2. In Figma → **Plugins → Development → Import plugin from manifest…**
3. Pick `figma-plugin/manifest.json` from this repo.
4. Run the plugin (Plugins → Development → Tokens Studio MCP Bridge)
in the file you want to remap. The plugin UI should turn green
("Connected") once the MCP server's bridge is running.
The plugin opens a WebSocket to `ws://localhost:3055`, which the MCP
server starts on demand the first time you call `bridge_status` or
`apply_token_remap`. Only one Figma window can be connected at a time.
See [CHANGELOG.md](./CHANGELOG.md) for the v0.1 → v0.2 history.
---
## License
MIT — see [LICENSE](./LICENSE).
TDQS
Scored across 38 tools
Every tool has a clearly distinct purpose. Despite the large number of tools, each targets a specific operation (e.g., token CRUD, theme application, node manipulation, diagnostics) with no overlap in functionality. Descriptions clearly differentiate similar-sounding tools like apply_theme and apply_token_remap.
All tool names follow a consistent verb_noun pattern (e.g., analyze_design, apply_theme, set_token, delete_token). No mixing of conventions like camelCase or inconsistent verb forms. The pattern is predictable across all 38 tools.
38 tools is on the higher side, but the domain is complex (design tokens, Figma operations, version control, diagnostics). Some tools could be consolidated (e.g., multiple get_* tools for design context), but overall the count is reasonable for the scope. Borderline between slightly over and well-scoped.
The tool set covers CRUD for tokens, theme application, node manipulation, image/icon creation, accessibility audits, version control, and extensive diagnostics. Minor gaps like bulk token import from a file are covered indirectly (e.g., propose_token_remap). The set is nearly complete for the intended use case.