Working with the Pencil MCP Server
Non-negotiable rules
.penfiles are JSON-based, human-readable, and Git-diffable. You *can* read them with standard filesystem tools, but always use Pencil MCP tools (batch_get,batch_design, etc.) for design operations — they handle IDs correctly, apply layout calculations, and prevent format-breaking manual edits.- Avoid hand-editing a
.penfile directly. Manual edits can break internal references, component linkages, or variable bindings — even though the format is plain JSON. - When in doubt about a node's ID or structure, look it up — never guess node IDs.
Starting every task
Begin with get_editor_state() unless the user has given you an explicit file path. This tells you:
- Which
.penfile is currently open and active - The current user selection (selected node IDs)
- Other essential context (canvas state, viewport)
If no document is open, call open_document("new") to create a blank canvas, or open_document("<path>") to open a specific file.
Core workflow
1. Understand the canvas
Before placing anything, understand what's already there:
batch_get(patterns, nodeIds)— discover nodes by name patterns (e.g.,["*header*", "*nav*"]) or by specific IDs. This is your primary exploration tool.snapshot_layout()— get computed bounding rectangles for every node. Use this to understand spatial relationships and decide where to insert new content.find_empty_space_on_canvas(direction, size)— find open areas on the canvas when you need to add new screens or components without overlap.
2. Get design direction
When building screens from scratch or styling existing content, orient yourself with guidelines and a style guide:
get_guidelines(topic) # topic: code | table | tailwind | landing-page | slides |
# design-system | mobile-app | web-appAfter guidelines, use get_style_guide_tags() to see available tags, then get_style_guide(tags, name) to load a matching style guide. Style guides provide color palettes, typography, and component patterns that make designs cohesive.
3. Design or edit
All mutations go through batch_design(operations). This accepts a script of operations — aim for up to 25 per call. Make operations meaningful and group logically related changes together.
4. Validate visually
After significant changes, call get_screenshot(nodeId?) to see the result. Use this to catch layout issues, misaligned elements, or off-palette colors before moving on. Don't skip this step after major design blocks.
batch_design operation syntax
Every line is a single operation. Variables assigned in one line are usable in subsequent lines of the same call.
Insert — create a new node
foo = I("parentId", { ...properties })Copy — clone an existing node into a new parent
bar = C("sourceNodeId", "parentId", { ...overrides })Replace — swap out one or more nodes with new content
R("nodeId1/nodeId2", { ...properties })Update — mutate properties of an existing node
U("nodeId", { ...properties })
U(foo + "/childId", { ...properties }) # combine with variable from earlier lineDelete
D("nodeId")Move — reorder or reparent a node
M("nodeId", "newParentId", insertionIndex)Generate image with AI
G("nodeId", "ai", "descriptive prompt for the image")Tip: Assign results to variables (foo = I(...)) whenever you'll reference those nodes later in the same batch. String concatenation with / navigates into children.
Variables and themes
get_variables()— read all design tokens (colors, spacing, font sizes) and themes currently defined in the file.set_variables(variables)— add or update tokens. Changing a token here propagates to every node that references it — this is the right way to do global rebranding.
Always read variables before touching colors or typography, so you use the proper token names rather than raw hex values.
Bulk find-and-replace
When you need to change a property everywhere (e.g., swap a font family across the whole design):
search_all_unique_properties(parentIds)— discover every distinct property value present under those parents. Shows you what you're working with.replace_all_matching_properties(parentIds, matchProps, newProps)— replace all nodes that match a property pattern with new values. Far more reliable than identifying and updating each node individually.
Exporting
export_nodes(nodeIds, format, outputFolder)
# format: "PNG" | "JPEG" | "WEBP" | "PDF"Always confirm node IDs via batch_get before exporting — exporting the wrong node is a common mistake.
Supported AI assistants
Pencil works with multiple AI tools through MCP. Not all tools support every feature — see references/setup-troubleshooting.md for installation and connection details.
- Claude Code (CLI and IDE)
- Claude Desktop
- Cursor (AI-powered IDE)
- Windsurf IDE (Codeium)
- Codex CLI (OpenAI)
- Antigravity IDE
- OpenCode CLI
The MCP server runs locally — no cloud dependency for design operations. AI assistants connect via MCP when Pencil is running. View available tools in your IDE's MCP settings.
Components and instances
Any element can become a reusable component:
- Select the element on the canvas
- Press
Cmd/Ctrl + Option/Alt + K(or click "Create component" in the properties panel) - The element becomes the component origin, shown with a magenta bounding box
To create an instance, copy the component origin on the canvas. Instances show a violet bounding box. Click "Go to component" in the properties panel to navigate back to the origin.
To detach an instance (break the link to its origin): Cmd/Ctrl + Option/Alt + X.
Via MCP, components are objects with reusable: true and instances use type: "ref". See references/pen-format.md for the full component/instance/override model.
Slots
Slots are designated areas within a component where elements can be dropped in.
Creating a slot (UI):
- Create a frame inside a component origin
- Keep the frame empty
- Click "Make a slot" in the properties panel
Slots appear with diagonal lines on the canvas. You can mark other components as "suggested slot components" — for example, a table component's slot can suggest table-row as content. This guides both human and AI designers.
Via MCP, slots are defined with the slot property on a frame (an array of suggested component IDs). See references/pen-format.md.
Design libraries
Design libraries are collections of reusable components that can be shared across .pen files.
Creating a library:
- Create a
.penfile and populate it with components - In the layers panel, click the "Libraries" icon
- Click "Turn this file into a library" — the file becomes
.lib.pen
Once a file is marked as a library, this cannot be undone.
Using a library:
- In the layers panel → Libraries icon → select the library to import
- In the layers panel → Assets icon → drag and drop components onto the canvas
Changes to components in a library file propagate to every file that uses them.
Importing from Figma
Pencil can import Figma designs:
- Complete.fig files: Toolbar → click chevron below Rectangle icon → "Import Figma" (or File → Import Image/SVG/Figma in the desktop app)
- Individual layers: Copy elements in Figma, paste onto the Pencil canvas
Note: Copying and pasting *image* elements from Figma is not supported. Import the complete file or add images manually.
Image import: Drag-and-drop, copy-paste, or toolbar/file menu. Supported formats: PNG, JPEG, SVG.
Built-in icon libraries: Material Symbols (Outlined, Rounded, Sharp), Lucide Icons, Feather, Phosphor. Custom SVG icons can be imported the same way as images.
Design quality principles
- Use the style guide. Don't hardcode colors or fonts unless the user explicitly provides them. Load a style guide that matches the context (landing-page, mobile-app, etc.) and use its tokens.
- Respect existing structure. Read the canvas with
batch_getandsnapshot_layoutbefore adding things — inserting into the wrong parent or overlapping existing content wastes iterations. - Validate after big changes.
get_screenshotis cheap. Use it to catch problems early rather than building several more layers on top of a broken foundation. - Keep batches focused. Grouping 25 tightly related operations is good. Cramming unrelated sections into one giant call makes it hard to debug if something goes wrong.
- Avoid guessing IDs. Node IDs look like
dfFAeg2. Always discover them viabatch_getor from the result of a previousI()orC()call. Random strings will silently target the wrong node or fail.
Common task patterns
Add a new screen to an existing file
1. get_editor_state() # learn active file
2. find_empty_space_on_canvas(direction, size) # find where to put it
3. get_guidelines("web-app") # or mobile-app, landing-page, etc.
4. get_style_guide_tags() → get_style_guide() # visual direction
5. batch_design(...) # build the screen
6. get_screenshot(newRootNodeId) # validateRestyle globally (e.g., change brand colors)
1. get_variables() # see existing tokens
2. set_variables({ primaryColor: "#new" }) # update the token
3. search_all_unique_properties([rootId]) # verify propagation
4. get_screenshot() # confirm visuallyExplore an unfamiliar design
1. get_editor_state()
2. batch_get(["*"], []) # broad pattern match
3. snapshot_layout() # spatial contextExport assets for handoff
1. batch_get(["*icon*", "*logo*"]) # find the right nodes
2. export_nodes([id1, id2], "PNG", "~/Desktop/assets")Quick reference — tool checklist
| Goal | Tool |
|---|---|
| Understand current state | get_editor_state |
| Open / create file | open_document |
| Find nodes by name | batch_get(patterns) |
| Find nodes by ID | batch_get([], nodeIds) |
| Understand layout & positions | snapshot_layout |
| Find free canvas space | find_empty_space_on_canvas |
| Load design guidelines | get_guidelines(topic) |
| Load visual style | get_style_guide_tags → get_style_guide |
| Create / edit / delete nodes | batch_design |
| See the result | get_screenshot |
| Read design tokens | get_variables |
| Change design tokens | set_variables |
| Find all property values | search_all_unique_properties |
| Bulk property replacement | replace_all_matching_properties |
| Export to image/pdf | export_nodes |
Reference files
| Topic | File |
|---|---|
| .pen file format & TypeScript schema | references/pen-format.md |
| Installation, authentication & troubleshooting | references/setup-troubleshooting.md |
| Keyboard shortcuts | references/keyboard-shortcuts.md |
| Design → Code: Web (React, Tailwind, Vue, Angular, HTML/CSS) | references/web.md |
| Design → Code: Android (Jetpack Compose, XML) | references/android.md |
| Design → Code: iOS / macOS (SwiftUI, UIKit, AppKit) | references/ios-macos.md |
| Design → Code: Windows (WinUI 3, WPF, MAUI, XAML) | references/windows.md |
| Design → Code: Linux (GTK 4, Qt, QML) | references/linux.md |
Design → Code
Pencil designs can be translated into production-ready code for all major platforms:
| Platform | Framework | Pencil support |
|---|---|---|
| Web | React + Tailwind | ✅ Native guidelines (get_guidelines("code") + get_guidelines("tailwind")) |
| Web | Vue, Angular, Svelte, vanilla HTML/CSS/JS | ✅ Via design extraction |
| Android | Jetpack Compose, XML layouts | ✅ Via design extraction |
| iOS | SwiftUI, UIKit | ✅ Via design extraction |
| macOS | SwiftUI, AppKit | ✅ Via design extraction |
| Windows | WPF, WinUI 3, XAML | ✅ Via design extraction |
| Linux | GTK 4, Qt/QML | ✅ Via design extraction |
Extraction workflow
Run this for every platform before writing any code.
Step 1 — Understand the design
get_editor_state() # confirm which .pen file is open
batch_get(["*"], []) # discover all nodes; note IDs
snapshot_layout() # get x, y, width, height for every node
get_variables() # read colors, spacing, typography tokens
get_screenshot(rootNodeId) # visual referenceStep 2 — Extract component details
For each component you need to implement:
batch_get([], ["nodeId"], { maxDepth: 10, includePathGeometry: true })Key properties to record:
type— frame, text, image, shape, component_ref, etc.fill,stroke,cornerRadius,opacity— appearancefontFamily,fontSize,fontWeight,textAlign,lineHeight— typographylayoutMode(horizontal/vertical),padding*,itemSpacing— layoutwidth,height,x,y(fromsnapshot_layout) — size & positionsizing(fill_container/fit_content/ fixed) — how to size the nodechildren— nested structuregeometry— SVG paths (icons, logos)
Step 3 — Map to platform concepts
Load the reference file that matches the target platform before writing any code:
| Target | File to read |
|---|---|
| React / Tailwind (preferred web) | references/web.md |
| Vue / Angular / HTML+CSS+JS | references/web.md |
| Android (Jetpack Compose or XML) | references/android.md |
| iOS / macOS (SwiftUI or UIKit/AppKit) | references/ios-macos.md |
| Windows (WPF / WinUI 3 / XAML) | references/windows.md |
| Linux (GTK 4 / Qt / QML) | references/linux.md |
If the user hasn't specified a platform, ask. If the target codebase already exists, inspect it first to detect the framework in use — always match the project's existing stack.
Step 4 — Validate
After generating code, call get_screenshot on the relevant node and compare visually. Any obvious mismatches in color, spacing, or layout need fixing before hand-off.
Design-to-code principles
- Match the project. If a codebase already exists, explore it to find the framework, styling approach, existing components, and conventions. Update existing components instead of creating duplicates.
- Use design tokens, not hardcoded values. Read
get_variables()and map token names to the project's theming system. - Preserve content exactly. Use the same text labels, icon names, and spacing as in the design.
- No documentation files. After generating code, do not create Markdown changelogs or README updates unless the user explicitly asks.
- Validate visually. Use
get_screenshotto compare design vs. implementation and iterate until they match.
Code → Design
Pencil supports importing existing code into designs. The AI agent can read your source files and recreate them visually in a .pen file.
Requirements:
- Keep the
.penfile in the same workspace as your code so the AI agent can access both - Have Pencil and the AI assistant running
Workflow:
- Open your
.penfile - Open AI chat (
Cmd/Ctrl + K) - Ask to import code:
Recreate the Button component from src/components/Button.tsx
Import the LoginForm from my codebase into this design
Add the Header component from src/layouts/Header.tsxWhat gets imported: component structure/hierarchy, layout/positioning, styling (colors, typography, spacing).
Two-way sync
The most powerful workflow combines Design → Code and Code → Design:
- Start with code — Import existing components into Pencil
- Design improvements — Make visual changes in Pencil
- Update code — Ask AI to apply changes back to code
- Iterate — Repeat as needed
CSS variables ↔ Pencil variables
Create a synchronized design token system:
Import CSS to Pencil:
Create Pencil variables from my globals.css
Import design tokens from src/styles/tokens.cssExport Pencil to CSS:
Update globals.css with these Pencil variables
Sync these design tokens to my CSSRecommended workflows
Start new features:
- Design in Pencil first → generate initial code → refine implementation → update design if needed
Update existing features:
- Import component into Pencil → make design changes → sync changes back to code
Design system maintenance:
- Define variables in Pencil → sync to CSS → use variables in both design and code → update once, apply everywhere
File organization
Keep .pen files alongside code so the AI agent can see both:
my-project/
├── src/
│ ├── components/
│ └── styles/
├── design.pen ← Design file in the same repo
└── package.json