Patch Penpot MCP: add design-token write endpoints (create/update color + typography + dimension tokens, themes) #60

Closed
opened 2026-04-18 17:42:10 +00:00 by claude-desktop · 0 comments
Collaborator

User story

As the designer agent (from #56), I want to create and modify design tokens — colors, typographies, dimensions, themes — programmatically through the Penpot MCP, so that when I open a new design file I can seed a proper token-backed palette instead of drawing visual swatches as regular shapes.

Context

Penpot has first-class design-token support (Tokens tab in the UI: Border Radius, Color, Dimensions, Font Family, Font Size, Font Weight, Letter Spacing, Number, Opacity, Rotation, Shadow, Sizing, Spacing, Stroke Width, Text Case, Text Decoration, Typography + Themes + Collections). The internal format is DTCG-compatible (Design Tokens Community Group / Tokens Studio).

Our patched MCP at ~/Workspace/penpot-mcp-server/ currently exposes:

  • get_design_tokens(file_id)read path, DB-only, fails on our OIDC Penpot instance because the DB pool isn't initialized (see #57's context; same symptom as list_teams).
  • No write paths at all. No create_color, update_color, create_theme, apply_token_to_shape, nothing.

Consequence on #55: we drew 24 colored rectangles + 72 labels to mimic a token palette visually. That's not real tokens — shapes can't bind to the palette, theme-switch is impossible, and exporting for CSS/Style Dictionary means the operator copy-pastes from a handoff comment instead of pointing a tool at a JSON file.

The designer agent from #56 is blocked from doing this properly until the MCP can write tokens.

Short-term workaround (landing in parallel): hand-authored DTCG JSON at design/tokens.json in this repo, user imports it via Penpot's Tokens tab UI. Works for today but not sustainable for agent-driven design.

Acceptance criteria

MCP — new tools

Under penpot-mcp-server/src/penpot_mcp/tools/ (or equivalent):

  • create_color_token(file_id, set_name, name, value, description?) — inserts a color token into the given token set (creating the set if missing).
  • update_color_token(file_id, set_name, name, value) — updates an existing token; errors if missing.
  • delete_color_token(file_id, set_name, name) — removes a token.
  • create_typography_token(file_id, set_name, name, font_family, font_size, font_weight, letter_spacing?, line_height?) — typography composite.
  • create_dimension_token(file_id, set_name, name, value, type)typespacing / sizing / borderRadius / strokeWidth etc.
  • create_theme(file_id, theme_name, set_names[]) — bundles token sets into a named theme.
  • set_active_theme(file_id, theme_name) — marks which theme is active in the file's metadata.
  • import_tokens_dtcg(file_id, json_string) — bulk import from DTCG / Tokens Studio JSON; one shot covers everything above.

MCP — fallback behavior

  • DB-first / RPC-fallback path on the read side: make get_design_tokens work on the OIDC instance (mirrors the get_file_info RPC fallback we landed on #55). If the DB pool is None, fall through to api.command("get-file", ...) and pluck the tokens-lib section.

Write path — implementation strategy

Two viable approaches, pick one:

  1. Direct Penpot RPC — Penpot's update-file command accepts mod-token / add-token / del-token change operations. This is how the UI itself does it. Mirrors the existing changes.py style — cleanest, zero new dependencies.
  2. Plugin script via execute_plugin_script — Penpot's plugin API has library.colors.createColor(...), library.typographies.createTypography(...), etc. Works but adds a runtime layer.

Strong preference: (1) direct RPC — matches the rest of services/api.py, same auth model, same apply_changes flow.

Tests

  • Unit: each new tool round-trips a token (create → get_design_tokens → assert present → delete → assert absent).
  • Import: feed the design/tokens.json bundle from this repo, check all theme-dark colors exist in the file afterwards.
  • Bulk: create 50 tokens in parallel, assert no revision-conflict errors (the apply_changes revn handling must cover token writes).

Image + deployment

  • claude-hooks:dev rebuild picks up the new tools (since penpot-mcp-server/ is baked in per #57's infra work or its predecessor).
  • The designer agent container (#56) sees the new tools via the Penpot MCP binding.

Documentation

  • penpot-mcp-server/README.md: document the new token tools.
  • penpot-mcp-server/CHANGELOG.md (or equivalent): note the addition.
  • If we upstream the MCP patches (out of scope for this ticket), queue a PR for the public repo.

Out of scope

  • UI-side token editing (that's Penpot's existing Tokens tab).
  • Importing non-DTCG formats (Figma Tokens, Style Dictionary YAML, etc.) — the import_tokens_dtcg tool is enough.
  • Binding existing shapes to tokens retroactively — shapes created pre-token stay as literal hex. Future story.
  • Multi-file token libraries / shared libraries across files — Penpot supports it, but we only need per-file tokens for now.

References

  • Debugging context on #55 where we discovered the gap (drew 24 swatches manually instead of tokens).
  • Workaround file: design/tokens.json in this repo — DTCG JSON the operator imports manually via Penpot UI.
  • Penpot RPC change-op reference: the existing services/changes.py in our patched MCP (same patterns, new change types).
  • Penpot UI Tokens tab: file → left panel → Tokens (third tab after Layers, Resources).
  • Tokens Studio / DTCG format: https://design-tokens.github.io/community-group/format/
  • Tracking issue: #47. Milestone: Agent pool + customization (#16).

Dependencies

  • Blocked by: #56 (designer agent type lands + image is set up to carry the patched MCP). Technically this ticket could land first and sit unused, but it's more coherent sequenced after #56.
  • Blocks: full agent-driven design workflow (designer agent seeding its own token palette on every new file instead of asking the operator to import a JSON).
  • Branch off: main, or the penpot-mcp-server/ vendored copy if we fork it into the claude-hooks repo.
## User story As the **designer agent** (from #56), I want to create and modify design tokens — colors, typographies, dimensions, themes — programmatically through the Penpot MCP, so that when I open a new design file I can seed a proper token-backed palette instead of drawing visual swatches as regular shapes. ## Context Penpot has first-class design-token support (Tokens tab in the UI: Border Radius, Color, Dimensions, Font Family, Font Size, Font Weight, Letter Spacing, Number, Opacity, Rotation, Shadow, Sizing, Spacing, Stroke Width, Text Case, Text Decoration, Typography + Themes + Collections). The internal format is DTCG-compatible (Design Tokens Community Group / Tokens Studio). Our patched MCP at `~/Workspace/penpot-mcp-server/` currently exposes: - `get_design_tokens(file_id)` — **read path, DB-only, fails on our OIDC Penpot instance** because the DB pool isn't initialized (see #57's context; same symptom as `list_teams`). - **No write paths at all.** No `create_color`, `update_color`, `create_theme`, `apply_token_to_shape`, nothing. Consequence on #55: we drew 24 colored rectangles + 72 labels to mimic a token palette visually. That's not real tokens — shapes can't bind to the palette, theme-switch is impossible, and exporting for CSS/Style Dictionary means the operator copy-pastes from a handoff comment instead of pointing a tool at a JSON file. The `designer` agent from #56 is blocked from doing this properly until the MCP can write tokens. Short-term workaround (landing in parallel): hand-authored DTCG JSON at `design/tokens.json` in this repo, user imports it via Penpot's Tokens tab UI. Works for today but not sustainable for agent-driven design. ## Acceptance criteria ### MCP — new tools Under `penpot-mcp-server/src/penpot_mcp/tools/` (or equivalent): - [ ] `create_color_token(file_id, set_name, name, value, description?)` — inserts a color token into the given token set (creating the set if missing). - [ ] `update_color_token(file_id, set_name, name, value)` — updates an existing token; errors if missing. - [ ] `delete_color_token(file_id, set_name, name)` — removes a token. - [ ] `create_typography_token(file_id, set_name, name, font_family, font_size, font_weight, letter_spacing?, line_height?)` — typography composite. - [ ] `create_dimension_token(file_id, set_name, name, value, type)` — `type` ∈ `spacing` / `sizing` / `borderRadius` / `strokeWidth` etc. - [ ] `create_theme(file_id, theme_name, set_names[])` — bundles token sets into a named theme. - [ ] `set_active_theme(file_id, theme_name)` — marks which theme is active in the file's metadata. - [ ] `import_tokens_dtcg(file_id, json_string)` — bulk import from DTCG / Tokens Studio JSON; one shot covers everything above. ### MCP — fallback behavior - [ ] DB-first / RPC-fallback path on the read side: make `get_design_tokens` work on the OIDC instance (mirrors the `get_file_info` RPC fallback we landed on #55). If the DB pool is `None`, fall through to `api.command("get-file", ...)` and pluck the `tokens-lib` section. ### Write path — implementation strategy Two viable approaches, pick one: 1. **Direct Penpot RPC** — Penpot's `update-file` command accepts `mod-token` / `add-token` / `del-token` change operations. This is how the UI itself does it. Mirrors the existing `changes.py` style — cleanest, zero new dependencies. 2. **Plugin script via `execute_plugin_script`** — Penpot's plugin API has `library.colors.createColor(...)`, `library.typographies.createTypography(...)`, etc. Works but adds a runtime layer. Strong preference: **(1) direct RPC** — matches the rest of `services/api.py`, same auth model, same `apply_changes` flow. ### Tests - [ ] Unit: each new tool round-trips a token (create → `get_design_tokens` → assert present → delete → assert absent). - [ ] Import: feed the `design/tokens.json` bundle from this repo, check all theme-dark colors exist in the file afterwards. - [ ] Bulk: create 50 tokens in parallel, assert no revision-conflict errors (the `apply_changes` revn handling must cover token writes). ### Image + deployment - [ ] `claude-hooks:dev` rebuild picks up the new tools (since `penpot-mcp-server/` is baked in per #57's infra work or its predecessor). - [ ] The `designer` agent container (#56) sees the new tools via the Penpot MCP binding. ### Documentation - [ ] `penpot-mcp-server/README.md`: document the new token tools. - [ ] `penpot-mcp-server/CHANGELOG.md` (or equivalent): note the addition. - [ ] If we upstream the MCP patches (out of scope for this ticket), queue a PR for the public repo. ## Out of scope - UI-side token editing (that's Penpot's existing Tokens tab). - Importing non-DTCG formats (Figma Tokens, Style Dictionary YAML, etc.) — the `import_tokens_dtcg` tool is enough. - Binding existing shapes to tokens retroactively — shapes created pre-token stay as literal hex. Future story. - Multi-file token libraries / shared libraries across files — Penpot supports it, but we only need per-file tokens for now. ## References - Debugging context on #55 where we discovered the gap (drew 24 swatches manually instead of tokens). - Workaround file: `design/tokens.json` in this repo — DTCG JSON the operator imports manually via Penpot UI. - Penpot RPC change-op reference: the existing `services/changes.py` in our patched MCP (same patterns, new change types). - Penpot UI Tokens tab: file → left panel → Tokens (third tab after Layers, Resources). - Tokens Studio / DTCG format: https://design-tokens.github.io/community-group/format/ - Tracking issue: #47. Milestone: Agent pool + customization (#16). ## Dependencies - **Blocked by:** #56 (designer agent type lands + image is set up to carry the patched MCP). Technically this ticket *could* land first and sit unused, but it's more coherent sequenced after #56. - **Blocks:** full agent-driven design workflow (designer agent seeding its own token palette on every new file instead of asking the operator to import a JSON). - **Branch off:** `main`, or the `penpot-mcp-server/` vendored copy if we fork it into the claude-hooks repo.
Sign in to join this conversation.
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
charles/claude-hooks#60
No description provided.