penpot-headless
Server Configuration
Describes the environment variables required to run the server.
| Name | Required | Description | Default |
|---|---|---|---|
| PENPOT_BASE_URL | Yes | The base URL of your Penpot instance (e.g., https://your-penpot-instance.example.com) | |
| PENPOT_TOKENS_PATH | Yes | Path to the project's token file (e.g., /path/to/your-project/design-tokens/tokens.json) | |
| PENPOT_ACCESS_TOKEN | Yes | Your Penpot access token generated from account settings |
Capabilities
Features and capabilities supported by this server
| Capability | Details |
|---|---|
| tools | {
"listChanged": true
} |
Tools
Functions exposed to the LLM to take actions
| Name | Description |
|---|---|
| penpot_list_teamsA | List all Penpot teams accessible to the configured access token. |
| penpot_list_projectsB | List all projects within a Penpot team. |
| penpot_list_filesB | List all files within a Penpot project. |
| penpot_create_projectC | Create a new project within a Penpot team. |
| penpot_rename_projectC | Rename an existing Penpot project. |
| penpot_delete_projectC | Delete a Penpot project. |
| penpot_create_fileC | Create a new file within a Penpot project. |
| penpot_rename_fileC | Rename an existing Penpot file. |
| penpot_delete_fileC | Delete a Penpot file. |
| penpot_get_file_snapshotA | Read a Penpot file, including its pages, shape tree, and current revn/vern (needed before any content mutation). |
| penpot_create_pageC | Create a new, empty page in a Penpot file. |
| penpot_list_pagesA | List all pages in a Penpot file, returning each page's id and name in order. |
| penpot_rename_pageB | Rename an existing page in a Penpot file. |
| penpot_delete_pageB | Delete a page from a Penpot file. The file must have at least two pages; deleting the last page is not allowed. |
| penpot_add_shapesB | Add one or more shapes (rect, frame, text, circle, path, bool, image) to a page in a Penpot file. "circle" is an ellipse bounded by x/y/width/height (width === height for a true circle). "path" takes a "content" array of path commands ({ command: "move-to"|"line-to"|"curve-to"|"close-path", params: {x,y,...} }); its bounding box is derived automatically from the commands. "bool" is a boolean operation (boolType: "union"|"difference"|"intersection"|"exclusion") over its children — add the bool shape first (with an explicit "id"), then add children with "parentId" matching that id (same pattern as frames); Penpot's editor computes the visual result when the file is opened. "image" displays an uploaded media object — first call penpot_upload_media to get a mediaId, then provide mediaId/mediaWidth/mediaHeight (source pixel dimensions) along with x/y/width/height for the canvas placement. Colors accept either a literal hex string or a { token: "name" } reference resolved against the project token file; the same applies to numeric spacing/radius fields (layout gaps/padding, layoutItem margins/min/max sizes, corner radii r1-r4), resolved against the token file's "spacing"/"radii" tables, and to a shape's "shadows" array (each entry either an inline { style, color, opacity, offsetX, offsetY, blur, spread } object or a { token: "name" } reference into the token file's "shadows" table). Shapes may be rotated via the "rotation" field (degrees, clockwise, about the shape's center). Frames may declare flex or grid auto-layout via "layout"; any shape may set "layoutItem" to control its own placement within an auto-layout parent (sizing, alignment, margins, and, for grid parents, row/column). |
| penpot_update_shapesA | Update one or more existing shapes in place, by id. Supports rect, frame, text, circle, bool, path, group, image, svg-raw, and any other shape type. Only the fields you pass are changed — everything else on the shape (child positions, component/variant tags, etc.) is left untouched. Geometry fields (x/y/width/height/rotation) automatically recompute the shape's selection box and transform, so partial geometry edits stay consistent. Exception: for path shapes, x/y/width/height are always derived from path content and cannot be set directly — provide "content" (an array of path commands) to relocate or reshape a path. For image shapes, use "mediaId"/"mediaWidth"/"mediaHeight"/"mtype" to swap the displayed image to a different already-uploaded media object. Colors, corner radii (r1-r4), and shadows each accept either a literal value or a { token: "name" } reference resolved against the project token file (colors/radii/shadows tables respectively); "clearStroke"/"clearShadows" remove strokes/shadows entirely (ignored if "stroke"/"shadows" is also given). "layout" (frame shapes only) sets or replaces the frame's auto-layout; "layoutItem" sets or replaces the shape's own placement within its parent auto-layout frame — both accept the same schema as penpot_add_shapes. Note: updating a group's x/y/width/height only adjusts the group's stored bounding box and does not move its children — use penpot_align_shapes or penpot_distribute_shapes to move a group as a unit. |
| penpot_delete_shapesA | Delete one or more existing shapes from a page in a Penpot file, by id. Deleting a frame or group also removes its children, matching Penpot's own delete behavior. This is the only way to remove a shape — there is no undo via this tool once the change is sent. |
| penpot_clone_shapesA | Duplicate one or more existing shapes (and, for frames/groups, their full descendant subtree) on a page, each with fresh ids. This is plain shape duplication — like Penpot's own Ctrl+D — not a component instance; use penpot_add_component_instance instead if you want a copy linked back to a component's main instance via shape-ref. Optional dx/dy offset each clone from its source (default: no offset, stacked directly on top of the original); optional parentId/frameId reparent every cloned root onto a new parent instead of staying alongside its source. If a cloned shape already carries component/variant tags (it is itself a component's main instance or an existing instance), those tags are carried over unchanged. |
| penpot_group_shapesA | Group one or more existing sibling shapes into a new group, matching Penpot's own Ctrl+G behavior. All supplied shapeIds must share the same parent on the page. The new group is inserted into the parent at the z-order position of the topmost selected shape; the grouped shapes become the group's children and keep their existing absolute canvas positions. Returns the new groupId and the parent's updated shapes order. Use penpot_ungroup_shapes to reverse this operation. |
| penpot_ungroup_shapesA | Dissolve a group, returning its children to the group's parent at the group's z-order position — matching Penpot's own Ctrl+Shift+G / "Ungroup" behavior. The supplied groupId must refer to a shape of type "group". After ungrouping, each former child keeps its absolute canvas position and gets the group's parent and frame as its new parent. The group shape itself is deleted. Returns the ids of the released children and the parent's updated shapes order. |
| penpot_reorder_shapesA | Change a shape's stacking (z-)order among its siblings, matching Penpot's own "Bring to front" / "Send to back" / "Forward" / "Backward" UI actions. Shapes have no explicit z-index — order is implicit in their parent's child list, where later entries render on top. "front"/"back" move the shape to the top/bottom of the stack; "forward"/"backward" swap it with the next/previous sibling (a no-op if already at that end); "before"/"after" place it immediately before/after another sibling given as targetId. Only reorders among existing siblings — does not reparent (use penpot_update_shapes's parentId/frameId for that). |
| penpot_align_shapesA | Align two or more shapes to a common edge or center, matching Penpot's own align actions — instead of the caller computing pixel positions itself from penpot_get_shape results. "edge" is one of: "left"/"right" (snap every shape's left/right edge to the leftmost/rightmost shape's), "top"/"bottom" (same, vertically), "center-h" (center every shape horizontally on the group's mid-x), or "center-v" (center vertically on the group's mid-y). Aligns on each shape's visible bounding box (its selrect), so rotated shapes line up by their rendered bounds; the group as a whole never moves (the reference line comes from the shapes' own extent). Applied as a single update-file change-set; shapes already on the reference line are left untouched. |
| penpot_distribute_shapesA | Distribute three or more shapes so the gaps between adjacent shapes are equal, matching Penpot's own "distribute horizontal/vertical spacing" actions — instead of the caller computing even spacing itself. "axis" is "horizontal" (equalize left-to-right gaps) or "vertical" (top-to-bottom). The two outermost shapes stay put and the ones between them slide so every gap is identical; distributes on each shape's visible bounding box (selrect), accounting for differing shape sizes. Applied as a single update-file change-set; shapes already evenly spaced (and the two endpoints) are left untouched. |
| penpot_batchA | Apply an ordered list of create/update/delete/reorder operations to a page as a single update-file change-set — one revn/vern round trip no matter how many shapes are touched, instead of one RPC call per shape (and the races on revn that come with that). Each op is one of: { op: "create", shape: }, { op: "update", patch: }, { op: "delete", shapeId }, or { op: "reorder", shapeId, action, targetId? } (same actions as penpot_reorder_shapes). Ops are applied in array order and each sees the effect of every earlier op in the same call — a "create" can set an explicit "id" (a UUID) and be referenced as a later shape's parentId/frameId (to build a frame and its children in one call), and a later op can update/delete/reorder a shape created earlier in the same batch. Returns one result entry per op, in the same order as "ops". |
| penpot_checkpointA | Snapshot shapes so a subsequent penpot_restore_checkpoint call can undo whatever happens between now and then — including a wrong penpot_delete_shapes call, which otherwise has no undo path short of Penpot's own UI. When pageId is supplied only that page is snapshotted; omit pageId to snapshot every page in the file (whole-file checkpoint). Reusable across multiple restores until explicitly discarded via penpot_discard_checkpoint. When the server is configured with PENPOT_CHECKPOINTS_PATH the checkpoint is also written to disk and will survive a server restart; without that setting it lives only in process memory and is lost on restart. Call this immediately before a risky multi-step edit; pass the returned checkpointId to penpot_restore_checkpoint to undo everything since. |
| penpot_restore_checkpointA | Undo every shape change made since a penpot_checkpoint call, by diffing the page's current state against the snapshot and replaying corrective changes as a single update-file call: shapes the snapshot has but the page no longer does are recreated verbatim, shapes the page has that the snapshot didn't are deleted, and shapes present in both are overwritten back to their snapshotted fields (geometry, fills, children, layout — everything). When the checkpoint was taken without a pageId (whole-file checkpoint), all snapshotted pages are restored in a single update-file call. The checkpoint itself is NOT consumed — it can be restored to again, or discarded explicitly via penpot_discard_checkpoint. Throws if the checkpoint id is unknown (already discarded, or the server restarted since it was taken). |
| penpot_discard_checkpointA | Free a checkpoint taken via penpot_checkpoint without restoring it, once it is no longer needed. Removes both the in-memory entry and, when disk persistence is enabled (PENPOT_CHECKPOINTS_PATH), the corresponding file on disk. |
| penpot_get_shapeA | Look up a single shape by id on a page, without pulling the whole page via penpot_get_file_snapshot. By default, nests the shape's full descendant subtree (frames/groups' children) under its "shapes" field instead of leaving them as bare ids; set includeDescendants to false for just the shape itself, or maxDepth to cap how many levels deep the nesting goes. Always includes a "componentInfo" field reporting the shape's component link state: "not-an-instance" (plain shape), "main-component-root" (the component's own main instance), "linked" (a placed copy linked to a component — also includes driftedFields listing any field names whose value on this instance differs from the main component's current definition), or "detached" (componentId present but the component no longer exists in this file). |
| penpot_find_shapesA | Search a page for shapes matching one or more predicates, instead of walking the tree returned by penpot_get_file_snapshot by hand. Combine "type", "name" (exact match), "nameContains" (case-insensitive substring), "textContains" (case-insensitive substring against text shapes' rendered characters), "isComponentInstance", and/or "isRoot" — all given filters must match (AND). Omit every filter to list every shape on the page. Returns each match's id, type, name, position/size, and component link state ("linkState": "not-an-instance" | "linked" | "detached" | "main-component-root"), without descendants (use penpot_get_shape on a match's id for its full subtree including detailed componentInfo with driftedFields). |
| penpot_replace_textA | Find and replace text across all text shapes on a page in a single update-file call. Searches each text run (leaf node) of every text shape for the literal "search" string and replaces every occurrence with "replacement". Matching is case-insensitive by default (set caseSensitive: true to override). Replacement is per text-run: a search string that spans two adjacent runs in the same paragraph will not be matched. Returns the ids and names of every shape that was modified and the total number of occurrences replaced. |
| penpot_upload_mediaA | Upload an image or other media asset to a Penpot file and return the media object metadata (id, width, height, mtype). The returned id is used as "mediaId" when creating an image shape via penpot_add_shapes (type: "image"). Supply exactly one of: "filePath" (a local filesystem path the MCP server can read), "url" (an HTTPS URL Penpot's server will fetch directly — nothing passes through the MCP server), or "dataBase64" (base64-encoded bytes, requires "mtype"). The media object is attached to the given Penpot file (isLocal: true by default). After uploading, pass the returned id/width/height/mtype directly into penpot_add_shapes as mediaId/mediaWidth/mediaHeight/mtype for the image shape. |
| penpot_measure_textA | Measure the real rendered width/height of a text string for a given font, without creating or touching any shape. Tries Google Fonts first (by family name, no API key needed); if the family is not on Google Fonts, searches all Penpot teams accessible to the configured token for a matching custom/team font. Computes glyph advance widths so the numbers match what Penpot would actually render — removing the guesswork around width/height when calling penpot_add_shapes or penpot_update_shapes for a text shape. Splits on explicit "\n" for multi-line text; pass maxWidth to also get word-wrapped line breaks for a fixed-width box. Returns { width, height, lineHeight, lines }, where width/height are the natural (or wrapped) bounding box and lines is a per-line breakdown of text/width. |
| penpot_load_token_configB | Read and validate the project design-token file, returning the resolved colors/fonts/spacing/radii/shadows tables. |
| penpot_create_componentA | Register a new shape tree as a Penpot component (its "main instance"). Accepts the same shape specs as penpot_add_shapes (rect/frame/text, with layout/rotation/etc.); give each shape an explicit "id" so children can nest under a sibling via matching "parentId"/"frameId". The one shape not parented to a sibling in this call becomes the component root. Returns the componentId, usable with penpot_add_component_instance to place copies elsewhere. |
| penpot_add_component_instanceA | Place a copy (instance) of an existing component at a new position on a page. Clones the component's full main-instance shape tree with fresh ids, linked back to the main via shape-ref so Penpot recognizes it as a component copy. For components from a connected shared-library file, pass the library file's id as libraryFileId (returned by penpot_list_components with includeLibraries: true). |
| penpot_create_variant_groupC | Create a Penpot variant group: a container frame physically grouping two or more component main instances that share property axes (e.g. a "Button" with Type=Primary/Secondary variants), enabling Penpot's variant swap UI/switchVariant on instances of these components. Each entry in "variants" accepts the same shape specs as penpot_create_component. Returns one componentId per variant, usable with penpot_add_component_instance. |
| penpot_add_variantB | Add a new variant to an already-existing variant group container (created via penpot_create_variant_group). Accepts the same shape specs as one entry in penpot_create_variant_group's "variants" array. The containerId must be the id of the variant group's container frame (returned as "containerId" by penpot_create_variant_group, or found via penpot_find_shapes/penpot_list_components). Returns the new componentId and mainInstanceId, usable with penpot_add_component_instance. |
| penpot_list_componentsA | List a file's existing components (from its components map), instead of requiring the caller to have created them itself in the same session or parse penpot_get_file_snapshot's data.components by hand. Each entry includes the componentId (usable with penpot_add_component_instance), name, mainInstanceId/mainInstancePage, and — for variant components — variantId/variantProperties. Set includeLibraries: true to also include components from connected shared-library files (each library component entry will carry a libraryFileId field for use with penpot_add_component_instance). |
| penpot_list_comment_threadsA | List all comment threads in a Penpot file. Returns each thread's id, position, content (the opening message), resolution status, and participant list. |
| penpot_get_commentsA | Get all reply comments within a comment thread. Returns each comment's id, content, author, and timestamps. |
| penpot_create_comment_threadA | Create a new comment thread pinned to a canvas position on a page. |
| penpot_create_commentB | Add a reply comment to an existing comment thread. |
| penpot_update_commentB | Edit the text content of an existing comment (the opening message of a thread or a reply). |
| penpot_resolve_comment_threadA | Mark a comment thread as resolved ( |
| penpot_delete_commentA | Delete a single comment (reply) from a thread. Only the comment author can delete their own comment. |
| penpot_delete_comment_threadA | Delete an entire comment thread and all its replies. Only the thread owner can delete it. |
| penpot_list_file_snapshotsA | List all named snapshots (version history) for a Penpot file. Returns each snapshot's id, label, revn, createdAt, createdBy, and lock state. |
| penpot_create_file_snapshotA | Create a named snapshot of the current state of a Penpot file (equivalent to "Save version" in the Penpot UI). The optional |
| penpot_restore_file_snapshotA | Restore a Penpot file to the state captured in a named snapshot (equivalent to "Restore version" in the Penpot UI). Penpot automatically creates a system backup snapshot of the current file state before applying the restore, so you can undo a restore by listing snapshots again and restoring the most recent system entry. Requires the |
| penpot_rename_file_snapshotA | Rename an existing user-created snapshot. Only snapshots with |
| penpot_delete_file_snapshotA | Delete a user-created snapshot. Only snapshots with |
| penpot_get_file_snapshot_dataA | Retrieve the full file content (pages, shape tree) as it existed at a specific snapshot — for read-only inspection or comparison, without modifying the live file. Returns the same structure as |
| penpot_lock_file_snapshotA | Lock a user-created snapshot to prevent accidental deletion. A locked snapshot cannot be deleted until it is unlocked via |
| penpot_unlock_file_snapshotA | Unlock a previously locked user-created snapshot, allowing it to be deleted again. Only the snapshot's creator can unlock it. |
Prompts
Interactive templates invoked by user choice
| Name | Description |
|---|---|
No prompts | |
Resources
Contextual data attached and managed by the client
| Name | Description |
|---|---|
No resources | |
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/tvolodi/penpot-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server