Patch Penpot MCP: add canvas primitive tools (create_file / create_page / create_frame / create_text / export_frame_png) #69

Closed
opened 2026-04-18 22:44:39 +00:00 by claude-desktop · 1 comment
Collaborator

User story

As the designer agent (from #56), I want the patched Penpot MCP to
expose canvas-primitive creation tools — file, page, frame, text, and
PNG export — so that I can actually build UI/UX mockups programmatically
instead of aborting every dispatch with "no create_* tools" (as
surfaced on #62 issuecomment-5443).

Context

The claude-hooks fork of penpot-mcp at penpot-mcp-server/ currently
exposes 10 tools, all of which either read files or mutate
design tokens:

get_file_info                   get_design_tokens
create_color_token              update_color_token       delete_color_token
create_typography_token         create_dimension_token
create_theme                    set_active_theme         import_tokens_dtcg

Every tool requires a file_id and operates on pre-existing Penpot
files via the update-file RPC with add-token / mod-token /
del-token change-ops.

Not present (what this ticket adds):

  • create_file — bootstrap a file the agent can write to
  • create_page, create_frame, create_text — the primitives the
    design-implement.md skill assumes exist
  • export_frame_png — the sanity-check step the skill runs before
    posting its handoff comment

Without these, the designer dispatched on #62 correctly aborts: it has
no way to create a smoke-hello page, an 800×600 hello-frame, or the
centred HELLO text the AC calls for, and refuses to invent work it
can't verify.

Infrastructure already in place

As of PRs #66, #67, and #68, the pipeline below the MCP tool layer is
fully wired:

  • area:design → designer webhook routing (tested)
  • Container mode enabled for designer / design-reviewer with
    penpot_mcp: true (dispatch into claude-hooks:dev)
  • Access-token auth to the now-Authelia-free Penpot instance
  • Accept: application/json on every RPC call (transit-json bug)
  • seedContainerClaudeJson ensures the CLI has an oauthAccount
    binding in its bind-mount dir

So the moment these tools exist, re-dispatching the designer on #62
should land the full AC in one run.

Acceptance criteria

MCP — new tools (penpot-mcp-server/src/penpot_mcp/tools/canvas.py)

Pattern mirrors tokens.py: each tool assembles the appropriate
change-op(s) and dispatches via api.apply_changes.

  • create_file(project_id, name) — top-level RPC (create-file),
    returns {file_id, name}.
  • create_page(file_id, name) — emits add-page change-op;
    returns {page_id, name}.
  • create_frame(file_id, page_id, name, x, y, width, height, fill_color?)
    — emits add-obj with type frame; returns {shape_id}.
  • create_text(file_id, page_id, parent_id, content, x, y, font_family, font_size, font_weight, fill_color)
    — emits add-obj with type text + the Penpot text content tree;
    returns {shape_id}.
  • export_frame_png(file_id, page_id, frame_id, scale?=1)
    renders the frame (RPC command TBD during implementation —
    probably get-file-object-thumbnail or the dedicated export
    command); returns {png_bytes_base64, width, height}.

MCP — supporting plumbing

  • list_projects(team_id) and list_teams() — needed by
    design-implement to find a target project before
    create_file. Simple RPC wrappers.
  • list_files(project_id) or search_files(name) — so the skill's
    "reuse existing file by name" rule is reachable.

Tests

  • Unit: each new tool round-trips against a mocked api.apply_changes
    that captures the change-op and verifies the shape.
  • Live: add a throwaway pytest.mark.live test against the
    existing claude-hooks — dashboard file
    (689d7fa4-f94b-81d4-8007-e39c5c82f66c) that creates a new
    temp page, adds a frame + text, exports a PNG, and deletes
    the page. Gated behind PENPOT_ACCESS_TOKEN env — no-op
    when unset.

Smoke (closes the #56 loop)

  • Re-dispatch on #62: designer creates the smoke-hello page,
    the 800×600 hello-frame with fill #1A1B26, the HELLO
    text with fill #C0CAF5 on the existing file UUID
    (689d7fa4-f94b-81d4-8007-e39c5c82f66c), then posts a
    handoff comment with the deep-link and token hexes.
  • Extend scripts/smoke-creds.sh designer with a shape-tool
    presence assertion (grep for mcp__penpot__create_frame in
    the tool listing).

Documentation

  • penpot-mcp-server/README.md: new Canvas tools section.
  • penpot-mcp-server/CHANGELOG.md: bump to 0.5.0 with the full
    tool list.

Out of scope

  • Binding existing shapes to design tokens — still a separate follow-up
    from #60's acceptance criteria.
  • Multi-file component libraries / shared component libraries.
  • Rich shape types (paths, groups with complex geometry, symbols).
    The five primitives above cover the design-implement skill's
    happy path; richer shapes can come later per-demand.
  • Upstreaming the patches — we bake into our own fork for now
    (see penpot-mcp-server/README.md § Patch history).

References

  • Parent story: #56.
  • Triggering failure: #62 issuecomment-5443
    — designer's 4th run, same tool-gap diagnostic.
  • Infra prerequisites:
    • PR #66 — unit smoke for MCP wiring
    • PR #67 — container mode + .claude.json seed
    • PR #68 — access-token auth + transit-json fix
  • Existing canonical file to test against: claude-hooks — dashboard
    (file-id = 689d7fa4-f94b-81d4-8007-e39c5c82f66c), created on #55.
  • Penpot RPC change-op reference: penpot-mcp-server/src/penpot_mcp/services/changes.py
    (existing pattern for token change-ops) and Penpot's upstream source
    for add-obj / add-page op shapes.

Dependencies

  • Blocked by: #56 infrastructure — already landed on main (PRs #66,
    #67, #68 merge).
  • Blocks: the designer agent producing any mockup that goes
    beyond tokens; closes the operational smoke on #62.
  • Branch off: main.

Suggested breakdown (for boss)

Roughly one PR per bullet:

  1. list_teams / list_projects / list_files reads (smallest,
    unlocks exploration).
  2. create_file + create_page (RPC + add-page change-op).
  3. create_frame (add-obj change-op for frame type).
  4. create_text (add-obj with the text-content subtree — probably
    the trickiest; Penpot text is a nested paragraph/run structure).
  5. export_frame_png (separate RPC, no change-op).
  6. Smoke script update + docs.
## User story As the **designer agent** (from #56), I want the patched Penpot MCP to expose canvas-primitive creation tools — file, page, frame, text, and PNG export — so that I can actually build UI/UX mockups programmatically instead of aborting every dispatch with "no `create_*` tools" (as surfaced on [#62 issuecomment-5443](https://forge.jacquin.app/charles/claude-hooks/issues/62#issuecomment-5443)). ## Context The claude-hooks fork of `penpot-mcp` at `penpot-mcp-server/` currently exposes **10 tools**, all of which either read files or mutate **design tokens**: ``` get_file_info get_design_tokens create_color_token update_color_token delete_color_token create_typography_token create_dimension_token create_theme set_active_theme import_tokens_dtcg ``` Every tool requires a `file_id` and operates on pre-existing Penpot files via the `update-file` RPC with `add-token` / `mod-token` / `del-token` change-ops. **Not present** (what this ticket adds): - `create_file` — bootstrap a file the agent can write to - `create_page`, `create_frame`, `create_text` — the primitives the `design-implement.md` skill assumes exist - `export_frame_png` — the sanity-check step the skill runs before posting its handoff comment Without these, the designer dispatched on #62 correctly aborts: it has no way to create a `smoke-hello` page, an 800×600 `hello-frame`, or the centred `HELLO` text the AC calls for, and refuses to invent work it can't verify. ### Infrastructure already in place As of PRs #66, #67, and #68, the pipeline below the MCP tool layer is fully wired: - `area:design → designer` webhook routing (tested) - Container mode enabled for `designer` / `design-reviewer` with `penpot_mcp: true` (dispatch into `claude-hooks:dev`) - Access-token auth to the now-Authelia-free Penpot instance - `Accept: application/json` on every RPC call (transit-json bug) - `seedContainerClaudeJson` ensures the CLI has an `oauthAccount` binding in its bind-mount dir So the moment these tools exist, re-dispatching the designer on #62 should land the full AC in one run. ## Acceptance criteria ### MCP — new tools (`penpot-mcp-server/src/penpot_mcp/tools/canvas.py`) Pattern mirrors `tokens.py`: each tool assembles the appropriate change-op(s) and dispatches via `api.apply_changes`. - [ ] `create_file(project_id, name)` — top-level RPC (`create-file`), returns `{file_id, name}`. - [ ] `create_page(file_id, name)` — emits `add-page` change-op; returns `{page_id, name}`. - [ ] `create_frame(file_id, page_id, name, x, y, width, height, fill_color?)` — emits `add-obj` with type `frame`; returns `{shape_id}`. - [ ] `create_text(file_id, page_id, parent_id, content, x, y, font_family, font_size, font_weight, fill_color)` — emits `add-obj` with type `text` + the Penpot text content tree; returns `{shape_id}`. - [ ] `export_frame_png(file_id, page_id, frame_id, scale?=1)` — renders the frame (RPC command TBD during implementation — probably `get-file-object-thumbnail` or the dedicated export command); returns `{png_bytes_base64, width, height}`. ### MCP — supporting plumbing - [ ] `list_projects(team_id)` and `list_teams()` — needed by `design-implement` to find a target project before `create_file`. Simple RPC wrappers. - [ ] `list_files(project_id)` or `search_files(name)` — so the skill's "reuse existing file by name" rule is reachable. ### Tests - [ ] Unit: each new tool round-trips against a mocked `api.apply_changes` that captures the change-op and verifies the shape. - [ ] Live: add a throwaway `pytest.mark.live` test against the existing `claude-hooks — dashboard` file (`689d7fa4-f94b-81d4-8007-e39c5c82f66c`) that creates a new temp page, adds a frame + text, exports a PNG, and deletes the page. Gated behind `PENPOT_ACCESS_TOKEN` env — no-op when unset. ### Smoke (closes the #56 loop) - [ ] Re-dispatch on #62: designer creates the `smoke-hello` page, the 800×600 `hello-frame` with fill `#1A1B26`, the `HELLO` text with fill `#C0CAF5` on the existing file UUID (`689d7fa4-f94b-81d4-8007-e39c5c82f66c`), then posts a handoff comment with the deep-link and token hexes. - [ ] Extend `scripts/smoke-creds.sh designer` with a shape-tool presence assertion (grep for `mcp__penpot__create_frame` in the tool listing). ### Documentation - [ ] `penpot-mcp-server/README.md`: new _Canvas tools_ section. - [ ] `penpot-mcp-server/CHANGELOG.md`: bump to 0.5.0 with the full tool list. ## Out of scope - Binding existing shapes to design tokens — still a separate follow-up from #60's acceptance criteria. - Multi-file component libraries / shared component libraries. - Rich shape types (paths, groups with complex geometry, symbols). The five primitives above cover the `design-implement` skill's happy path; richer shapes can come later per-demand. - Upstreaming the patches — we bake into our own fork for now (see penpot-mcp-server/README.md § _Patch history_). ## References - Parent story: #56. - Triggering failure: [#62 issuecomment-5443](https://forge.jacquin.app/charles/claude-hooks/issues/62#issuecomment-5443) — designer's 4th run, same tool-gap diagnostic. - Infra prerequisites: - PR #66 — unit smoke for MCP wiring - PR #67 — container mode + `.claude.json` seed - PR #68 — access-token auth + transit-json fix - Existing canonical file to test against: `claude-hooks — dashboard` (`file-id = 689d7fa4-f94b-81d4-8007-e39c5c82f66c`), created on #55. - Penpot RPC change-op reference: `penpot-mcp-server/src/penpot_mcp/services/changes.py` (existing pattern for token change-ops) and Penpot's upstream source for `add-obj` / `add-page` op shapes. ## Dependencies - **Blocked by:** #56 infrastructure — already landed on main (PRs #66, #67, #68 merge). - **Blocks:** the `designer` agent producing any mockup that goes beyond tokens; closes the operational smoke on #62. - **Branch off:** `main`. ## Suggested breakdown (for `boss`) Roughly one PR per bullet: 1. `list_teams` / `list_projects` / `list_files` reads (smallest, unlocks exploration). 2. `create_file` + `create_page` (RPC + `add-page` change-op). 3. `create_frame` (`add-obj` change-op for frame type). 4. `create_text` (`add-obj` with the text-content subtree — probably the trickiest; Penpot text is a nested paragraph/run structure). 5. `export_frame_png` (separate RPC, no change-op). 6. Smoke script update + docs.
Author
Collaborator

Mostly superseded — the canvas primitives landed before this ticket opened:

  • PR #71list_teams, list_projects, list_files, create_page, create_frame, create_text
  • PR #73create_file(project_id, name, features?)

penpot-mcp-server/src/penpot_mcp/tools/canvas.py (377 lines) exposes all of the above, and scripts/smoke-creds.sh already probes their presence.

Remaining scope = export_frame_png, which is carved out into #74. Closing this parent in favour of tracking the last bit there.

Mostly superseded — the canvas primitives landed before this ticket opened: - PR #71 — `list_teams`, `list_projects`, `list_files`, `create_page`, `create_frame`, `create_text` - PR #73 — `create_file(project_id, name, features?)` `penpot-mcp-server/src/penpot_mcp/tools/canvas.py` (377 lines) exposes all of the above, and `scripts/smoke-creds.sh` already probes their presence. Remaining scope = **`export_frame_png`**, which is carved out into **#74**. Closing this parent in favour of tracking the last bit there.
Sign in to join this conversation.
No milestone
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#69
No description provided.