open-pencil
GitHubOpenPencil CLI与MCP服务器,用于操作Figma .fig文件和编辑器。支持结构检查、节点查询、设计Token分析导出(PNG/SVG/JSX等)及程序化修改设计。
Trigger Scenarios
Install
npx skills add open-pencil/open-pencil --skill open-pencil -g -y
SKILL.md
Frontmatter
{
"name": "open-pencil",
"description": "Work with Figma .fig design files and the running OpenPencil editor — inspect structure, query nodes, analyze design tokens, export PNG\/SVG\/PDF\/JSX, and modify designs programmatically. Use when asked to open, inspect, export, analyze, or edit .fig files, or to control the running OpenPencil app."
}
OpenPencil
OpenPencil provides a CLI and MCP server for .fig design files and the running OpenPencil editor.
Use two modes:
- App mode — connect to the running OpenPencil editor by omitting the file argument.
- Headless mode — work with
.figfiles directly by passing a file path.
# App mode — operates on the document open in the editor
openpencil tree
# Headless mode — operates on a .fig file
openpencil tree design.fig
Use openpencil --help, per-command help, and the connected MCP server’s tool schemas to check the installed version’s capabilities. See the CLI reference and MCP guide for current documentation.
Requirements
# CLI
bun add -g @open-pencil/cli
# MCP server used by the desktop app and external MCP clients
bun add -g @open-pencil/mcp
The desktop app starts openpencil-mcp-http automatically in production Tauri builds when @open-pencil/mcp is installed globally and exposes automation on:
- HTTP/RPC:
http://127.0.0.1:7600 - WebSocket bridge:
ws://127.0.0.1:7601 - MCP Streamable HTTP:
http://127.0.0.1:7600/mcp
CLI Commands
openpencil --help
Common commands:
info— document overview: pages, node counts, fontstree— print hierarchy with types and sizespages— list pagesnode— detailed node properties by IDselection— current selection from the running appfind— find nodes by name/typequery— XPath selectors for node searchvariables— list variables and collectionstokens— variables as a CSS custom-property stylesheet, a Tailwind v4 theme, or W3C design token files (--format dtcg --out <folder>)export— export PNG/JPG/WEBP/SVG/PDF/JSX/.figconvert— convert between supported document formatsanalyze— colors, typography, spacing, repeated clusterslint— consistency, structure, and accessibility checksformats— supported document/export formatseval— execute JavaScript with the Figma Plugin APItool— list, describe, and call any MCP tool from the shell, in app or headless modedocuments— list, open, create, save, close, and activate documents in the running appundo/redo— step back through your own (automation) changes in the running appsettings— read and change editor settings in the running app
Inspect
openpencil info design.fig
openpencil tree design.fig
openpencil tree --page "Components" --depth 3 # app mode
openpencil pages design.fig
openpencil node design.fig --id 1:23
openpencil node --id 1:23 # app mode
openpencil selection --json
openpencil variables design.fig
openpencil variables --collection "Colors" --type COLOR
openpencil tokens design.fig --format tailwind > theme.css
openpencil tokens design.fig --format dtcg --out tokens # W3C design tokens; import_design_tokens reads them back
Search and XPath query
openpencil find design.fig --name "Button"
openpencil find --type FRAME # app mode
openpencil find design.fig --type TEXT --page "Home"
openpencil find design.fig --name "Card" --type COMPONENT --limit 50
openpencil query design.fig "//FRAME"
openpencil query design.fig "//FRAME[@width < 300]"
openpencil query design.fig "//TEXT[contains(@name, 'Button')]"
openpencil query design.fig "//COMPONENT[@stackMode]"
openpencil query design.fig "//COMPONENT//FRAME//TEXT"
openpencil query "//FRAME[@width > 1000]" # app mode
Common node types: FRAME, TEXT, RECTANGLE, ELLIPSE, VECTOR, GROUP, COMPONENT, COMPONENT_SET, INSTANCE, SECTION, LINE, STAR, POLYGON, SLICE, BOOLEAN_OPERATION.
Export and convert
openpencil export design.fig -o hero.png
openpencil export -o hero.png # app mode
openpencil export design.fig --node 1:23 -s 2 -o button@2x.png
openpencil export design.fig -f jpg -q 85 -o preview.jpg
openpencil export design.fig -f svg --node 1:23 -o icon.svg
openpencil export design.fig -f pdf -o page.pdf
openpencil export design.fig -f fig -o roundtrip.fig
openpencil export design.fig -f jsx -o component.jsx
openpencil export design.fig -f jsx --style tailwind -o component.tsx
openpencil export design.pen -f storybook -o src/stories --watch # stories + design images per component, re-exported on save
openpencil export 'src/**/*.pen' -f storybook --beside # stories next to each design file
openpencil export design.fig --thumbnail --width 1920 --height 1080
openpencil export --page "Components" -o components.png
openpencil convert design.fig -o design.pen
openpencil formats
Analyze and lint
openpencil analyze colors design.fig
openpencil analyze colors --similar --threshold 10 # app mode
openpencil analyze typography design.fig --group-by size
openpencil analyze spacing design.fig --grid 8
openpencil analyze clusters design.fig --min-count 3
openpencil lint design.fig
openpencil lint design.fig --json
openpencil lint design.fig --fix -o fixed.fig # bind matching color variables, round to whole pixels
In MCP and AI chat, lint lists findings with their fix and suggestions; lint_fix applies the safe fixes, and the first suggestion of each finding with suggestions: true.
Eval (Figma Plugin API)
Execute JavaScript against the document using a Figma Plugin API-compatible runtime:
openpencil eval design.fig -c 'figma.currentPage.findAll(n => n.type === "TEXT").length'
# App mode — modifies the live document in the editor
openpencil eval -c '
const buttons = figma.currentPage.findAll(n => n.name === "Button");
buttons.forEach(b => { b.cornerRadius = 8 });
buttons.length + " buttons updated"
'
# Modify and save to the same file
openpencil eval design.fig -w -c '
const texts = figma.currentPage.findAll(n => n.type === "TEXT");
texts.forEach(t => { t.fontSize = 16 });
'
# Save to a different file
openpencil eval design.fig -o modified.fig -c '...'
# Read code from stdin
echo 'figma.currentPage.children.map(n => n.name)' | openpencil eval design.fig --stdin
# Style part of a text, and read how it is styled
openpencil eval design.fig -w -c '
const title = figma.currentPage.findOne(n => n.type === "TEXT" && n.name === "Title");
title.setRangeFontName(0, 5, { family: "Inter", style: "Bold" });
title.setRangeFills(0, 5, [{ type: "SOLID", color: { r: 0.9, g: 0.1, b: 0.1 } }]);
title.getStyledTextSegments(["fontName", "fills"]).map(s => [s.characters, s.fontName.style])
'
Text styles follow Figma's per-character model: getStyledTextSegments(fields) reads runs, getRange…/setRange… read and set font, size, fills, letter spacing, line height, decorations, text case, hyperlinks, text and fill style IDs, and bound variables for [start, end), and a text's fontSize, fontName, fills, and similar read figma.mixed while characters differ. Check for it before using the value.
Next to figma, scripts get openpencil: what OpenPencil adds to the Figma API, in its style. A main component can behave as a Reka UI control, by its own property and slot names:
openpencil eval design.fig -w -c '
const set = figma.currentPage.findOne(n => n.type === "COMPONENT_SET" && n.name === "Switch");
const thumb = set.findOne(n => n.name === "Thumb");
const behaviour = openpencil.setBehaviour(set, "switch")
.bindValue("value", "State") // On/Off guessed from the variant values
.bindPart("thumb", thumb); // the frame becomes a slot
behaviour.states = "Interaction";
behaviour.missing // [] when the control is complete
'
openpencil.behaviourKinds lists every kind with its values and parts; openpencil.getBehaviour(node) reads one (a variant reads its set's); openpencil.createSlot(frame) makes a frame a slot. The set_behaviour, get_behaviour, and create_slot tools do the same over MCP, and design JSX writes controls with Reka's element names (Switch.Root, Switch.Thumb).
Comments people leave on the canvas are review feedback you can act on. Read the open ones, change the design, then answer and resolve each, naming yourself as the author:
openpencil eval design.fig -w -c '
openpencil.getComments({ resolved: false }).map(c => ({ id: c.id, text: c.text, layer: c.node?.name }))
'
# after the change:
openpencil eval design.fig -w -c 'openpencil.getComment("c_…").reply("Title is 24px now", { author: "Claude" }).resolve()'
openpencil.addComment(text, { node, page, x, y, author }) leaves a new one. Over MCP the same is get_comments, add_comment, reply_to_comment, and resolve_comment. Comment text is Markdown.
Statuses say where a section, frame, component, or instance stands, from the document's own list. Read the list, find what is ready, and move a layer on once your work on it is done:
openpencil eval design.fig -c 'openpencil.statuses.map(s => s.name)'
openpencil eval design.fig -w -c 'openpencil.setStatus(figma.currentPage.findOne(n => n.name === "Checkout"), "In review")'
openpencil.getStatus(node) reads one. Over MCP the same is get_statuses and set_status.
Diff
Compare nodes and documents, and apply patches:
openpencil diff create design.fig --from 1:23 --to 1:87 # JSX attribute patch
openpencil diff jsx design.fig --from 1:23 --to 1:87 # JSX structure
openpencil diff show 1:24 design.fig --attributes 'rounded={8}' > fix.diff
openpencil diff apply fix.diff design.fig --dry-run # fails on stale values
openpencil diff apply fix.diff design.fig --write
openpencil diff visual design.fig --from 1:23 --to 1:87 -o diff.png
openpencil diff files before.fig after.fig # exit 1 when different
Control the running app
openpencil documents list --json # tab IDs, paths, pages
openpencil documents open designs/landing.fig # new tab; --json returns target.documentId
openpencil documents new --path designs/draft.fig
openpencil documents activate tab-123 --page-id 0:4 # bring a tab to the front
openpencil documents save --document-id tab-123
openpencil documents close --document-id tab-123 --save # or --discard; fails on unsaved changes otherwise
openpencil undo --document-id tab-123 # also: redo
openpencil settings get --json
openpencil settings set editing.snapping.pixelGrid false
tool exposes every MCP tool, so the CLI is never limited to its dedicated commands:
openpencil tool list
openpencil tool describe set_fill # JSON Schema for the arguments
openpencil tool call set_fill --document-id tab-123 --args '{"id":"0:5","color":"#2563eb"}'
openpencil tool call create_page design.fig --args '{"name":"Icons"}' --write # headless
Every command that reports structured data supports --json when appropriate.
MCP Server
Stdio MCP clients
Use Bun by default:
{
"mcpServers": {
"open-pencil": {
"command": "bunx",
"args": ["openpencil-mcp"]
}
}
}
If @open-pencil/mcp is installed globally, direct binaries also work:
{
"mcpServers": {
"open-pencil": {
"command": "openpencil-mcp"
}
}
}
HTTP / Streamable HTTP
export PORT=7600
export OPENPENCIL_MCP_ROOT=/path/to/files # explicitly limit filesystem access
openpencil-mcp-http
# or: bunx openpencil-mcp-http
Authentication is enabled by default with an automatically generated token. OPENPENCIL_MCP_AUTH_TOKEN can supply an explicit non-empty token; an empty value disables authentication. If browser access needs CORS, set OPENPENCIL_MCP_CORS_ORIGIN to a trusted origin. Never combine wildcard CORS (*) with disabled authentication.
The CLI defaults the filesystem root to the home directory on Windows and the current working directory elsewhere. Set OPENPENCIL_MCP_ROOT to an explicit narrow directory rather than relying on that default.
MCP workflow
- Find or open a document —
list_documentsfor open tabs and their IDs;open_file { path }within the effective filesystem root, ornew_document {}. Passdocument_idandpage_idexplicitly instead of relying on the active tab;activate_document { document_id }brings a tab to the front when the user should see it. - Query —
get_page_tree,find_nodes,query_nodes,get_node,list_pages,get_current_page. - Inspect —
get_jsx,diff_jsx,diff_create,diff_visual,describe,export_image,export_svg,export_pdf. - Modify —
render,batch_update,update_node,set_fill,set_layout,create_shape,import_svg, etc. - Save/export —
save_file,export_image,export_svg,export_pdf, or CLIexport. - Close —
close_file { document_id, unsaved }closes a document tab after the workflow. With unsaved changes it fails unlessunsavedis"save"or"discard"; automation never prompts in the app.
Use undo / redo { document_id } to step back your own changes. They refuse when the newest step was made by the user in the editor; never work around that. get_settings and update_settings { settings } read and change editor preferences such as theme, language, and snapping; they never expose credentials, models, or tool access.
Browser-native WebMCP (experimental)
WebMCP is off by default. In Settings → MCP → WebMCP, choose Inspect for read-only tools or Edit for scoped changes. These controls are independent of local MCP settings. Supporting browsers expose inspection and undoable existing-layer/property and variable edits directly from the OpenPencil workspace through document.modelContext, without an MCP server connection. Discover this browser surface separately: it excludes structural creation/deletion, arbitrary JS/JSX execution, external assets, filesystem operations, and credentials.
Calls capture the active document/page. Cancellation prevents an edit from starting but does not reverse an already committed edit; use editor undo instead. Atomic edits require at most 10,000 nodes and variables combined, including when these same tools run through app AI/MCP. See the WebMCP guide for scope and browser requirements.
Tool discovery
Users configure local server tools in Settings → Tool access → Local MCP, independently from Built-in AI tool preferences. Local MCP changes require a server restart and stdio client reconnection; built-in AI changes apply to the next message. ACP and Pi agents use the MCP surface, not the direct-model AI tool selection. Tool switches are not a sandbox: enabled scripting tools can still perform operations whose dedicated tools are disabled.
Discover available tools and their arguments from the connected server or browser; availability varies by version and mode. Each tool's schema and execution/exposure metadata are authoritative. Numeric inputs accept numbers or numeric strings consistently, but reject non-finite values. Do not rely on a fixed tool count or a copied inventory.
Tool availability can depend on server mode.
open_file,save_file, and disk-writing export paths are scoped to the effective filesystem root; set it explicitly withOPENPENCIL_MCP_ROOT.
Key tools for agents
query_nodes— XPath selectors to find specific nodes without fetching the full tree.get_jsx— inspect any node as JSX in the same format accepted byrender.diff_jsx/diff_create— compare two nodes as a JSX line diff or as an appliable patch of JSX attributes;diff_showpreviews setting attributes anddiff_applyapplies a patch only if the nodes still match it.diff_visual— pixel diff between two rendered nodes; use it to confirm an edit changed only the intended region.describe— semantic analysis of role, visual style, layout, and design issues.batch_update— apply multiple node updates efficiently.export_image/export_svg/export_pdf— visual verification and deliverables.viewport_zoom_to_fit/viewport_set/viewport_get— move the user's view only when they ask to be shown something.get_codegen_prompt— retrieve OpenPencil's current JSX/codegen guidance.undo/redo— revert or reapply your newest change; they refuse to touch the user's edits.list_documents/activate_document— discover open tabs and show the one you worked on.
JSX Rendering
Read Design authoring before creating or modifying JSX designs. This bundled reference is generated from Core's authoring guidance, tested examples, and renderer metadata—the same reference used by chat and codegen prompts.
Use the render tool for JSX strings. Use only the APIs exposed by the installed eval environment; native library exports are not automatically scripting globals. The connected server's get_codegen_prompt provides its version's codegen and authoring guidance.
Tips
- Omit the file path to work with the document open in the running OpenPencil editor.
- Start with
infoorget_page_treeto understand the document. - Use
tree --depth 2orquery_nodesto avoid overwhelming output on large files. - Export specific nodes with
--nodefor faster visual checks. - Use
export_imageafter changes to verify visual quality. - The running editor shows each MCP session as an agent at the layers its tools touch, and follows it while it works when the user has Follow agents on. Leave the user's selection and view alone unless they ask to be shown something.
- Use
analyze colors --similarto find near-duplicate colors. - Use
openpencil tool callfor MCP tools without a dedicated CLI command, andevalfor Figma Plugin API operations not covered by any tool. - Use
--jsonwhen piping CLI output to scripts. - In app mode,
evaland MCP modifications are reflected live in the editor.
Version History
-
5decf62
Current 2026-10-19 16:16
新增图层状态功能,支持基于工作流的配置、UI展示及状态管理,增强设计与开发协作流程。
- 8131401 2026-09-22 20:47


