Communitygithub.com

zeke/paper-app-skill

在 Paper 设计应用 (paper.design) 中通过本地 MCP 服务器进行工作。当需要设计、制作原型或探索变体、编辑画板或幻灯片、截图或导出 PNG/PDF/视频、移动或克隆 Paper 文件、使用自定义字体或本地图像、或将 Paper 设计转换为代码时使用。涵盖代码模式特性、结果解析、导出和截图变通方法、画布组织以及 MCP 无法执行的操作。

paper-app-skill 是什么?

该技能使 AI 代理能够通过本地 MCP 服务器与 Paper 设计应用无缝交互。它提供了关于如何使用 write_html、export 和 update_styles 等工具的全面指导,同时记录了官方指南未涵盖的关键细节,例如并行调用陷阱、文件范围工具所需的 fileId 参数以及代码模式下截图限制的变通方法。该技能适用于 Claude Code 和类似平台,并包含用于解析工具输出、管理导出文件和跨会话重用代码的实用辅助函数。其目的是确保可靠的设计编辑、高效的画布组织以及从设计到代码的平滑过渡,同时避免常见错误。

兼容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/zeke/paper-app-skill/tree/HEAD/skills/paper-app

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

预览

来自技能 README

paper-app-skill preview

文档

Paper 应用

Paper 是一个设计工具,其画布由真实的 HTML 和 CSS 构成。Paper 桌面应用运行一个本地 MCP 服务器,让代理能够读取画布并将 HTML 作为设计节点写入其中。

服务器自带指南。本技能涵盖了该指南未提及的内容:在实际会话中通过实践积累的经验。

开始之前

  1. 检查工具是否存在:在代码模式下运行 await describeNamespace("mcp__paper")。如果命名空间为空或 ALL_TOOLS 中没有 mcp__paper__* 条目,则说明会话未连接,通常是因为导致会话开始时 Paper 未打开。服务器位于 http://127.0.0.1:29979/mcp,并且仅在 Paper 打开时响应。pi mcp list(从 bash 运行)显示服务器本身是否健康。如果健康,会话无法自行重新连接:请要求用户运行 /mcp reconnect paper(在 pi 中),如果无效则运行 /reload。
  2. 每个会话加载一次官方指南:get_guide({ topic: "paper-mcp-instructions" })。遵循指南:在新设计前发布设计简报,每次 write_html 调用编写一个视觉组,通过截图审查,完成后调用 finish_working_on_nodes。其他主题:mobile-status-bar、figma-import、image-generation。
  3. 查找文件。list_resources({ limit: 200 }) 会列出文件,无需打开特定文件。将 fileId 传递给每个文件范围调用。get_basic_info({ fileId }) 可以在非前台标签的文件上工作,因此很少需要 open_file(它会在用户应用中打开一个标签)。
  4. 如果项目有 AGENTS.md,请在探索之前在其中查找 Paper 文件 ID 和画布约定。

从代码模式调用 Paper

  • 在一个脚本中使用 await 或按顺序轮次运行依赖的 Paper 调用。切勿在同一轮次中将其作为并行工具调用发出。并行调用导致了以往会话中的大多数失败,并且每个失败都被误认为是 Paper 的错误:“Node not found”(兄弟调用刚刚创建的节点)、子节点顺序混乱、导出缺少最近的写入、以及 read 对尚未写入的导出返回 ENOENT。
  • 每个文件范围工具都需要在其代码模式声明中包含 fileId,即使服务器的说明说可以省略。省略会导致验证失败。只有 get_basic_info 和 get_selection 可以接受无 fileId。
  • 在首次使用某个工具之前运行 describeTool("mcp__paper__<name>")。参数形状不可猜测:get_font_family_info 接受 familyNames: string[],set_text_content 和 update_styles 接受 updates 数组,而 export 接受 nodes 映射。
  • 文件范围工具返回一个纯字符串:先是 { file, contentHash } 的头部 JSON 块,然后是正文。没有文件范围的工具(list_resources、create_file、open_file、get_guide)返回 { content: [{ type: "text", text }] }。不要从结果中通过正则匹配第一个 "id":它是文件 ID,将其作为节点 ID 传递会导致“Node not found”。
  • 文件范围的失败和验证错误会抛出异常。一些非文件范围的失败(例如“打开一个 Paper 文件以使用此工具。”)会通过 isError: true 解决。不要用 .catch(() => {}) 包装 Paper 调用:错误的参数形状会静默失败,编辑永远不会出现。
  • “Your Paper file is currently disconnected from the server” 表示写入失败,但读取仍然有效。在以往会话中,休眠和重试未能解决此问题。要求用户将 Paper 文件带回前台,然后重试。
  • 在代码模式下 get_screenshot 不返回图像:其结果被扁平化为头部字符串。请使用下面的 shot() 辅助函数,该函数在同一脚本中导出 PNG 并读取它。当测试工具直接通过 MCP 调用(而非通过代码模式)时,get_screenshot 图像确实会返回。

解析正文后的有用结果形状:

工具正文
create_artboard{ id, name, worldX, worldY, width, height }
write_html{ createdNodes: [{ id, name, component }] },仅顶层节点
get_tree_summary{ summary },一个带缩进的文本树,格式为 Type "name" (id) W×H
get_children{ children: [{ id, name, component, childCount, x, y }], count }
find_nodes{ nodes: [{ id, name, component, matched: [{ styleName, styleValue }] }] }
duplicate_nodes{ duplicatedNodes: [{ sourceId, newId, descendantIdMap }] }
export{ exports: [{ nodeId, filePath }] }
update_styles当样式在相应上下文中无效时,列出 ignoredStyles。请检查它。

辅助函数

将这些粘贴到代码模式脚本的顶部,或存储一次并在后续调用中 eval:

var F = "<fileId>";
var txt = r => typeof r === "string" ? r : (r?.content ?? []).map(c => c.text ?? "").join("\n");
// 去除文件范围工具预先添加的 { file, contentHash } 头部。
var body = r => { const t = txt(r); const i = t.indexOf("\n}\n"); return t.startsWith('{\n  "file"') && i > -1 ? t.slice(i + 3) : t; };
var json = r => JSON.parse(body(r));
var w = async (target, html, mode = "insert-children") =>
  json(await tools.mcp__paper__write_html({ fileId: F, targetNodeId: target, mode, html })).createdNodes;
// 将节点导出为 PNG,显示它,然后从 ~/Downloads 删除该文件。
var shot = async (id, scale = "1x") => {
  const { exports } = json(await tools.mcp__paper__export({ fileId: F, nodes: { [id]: [{ format: "png", scale }] } }));
  const p = exports[0].filePath;
  image(await tools.read({ path: p, offset: null, limit: null }));
  await tools.bash({ command: `rm -f "${p}"`, timeout: 5 });
};

要在调用间重用辅助函数:store("paper", helperSource),然后在每个后续脚本顶部使用 eval(load("paper"))。使用 var 声明辅助函数,因为 eval 内的 const 和 let 不会泄露出去。store() 仅在脚本成功时保留值,因此请在一个不执行其他操作的脚本中存储辅助函数。

截图和导出

  • 在单独的代码模式调用中进行截图,与编辑分开。如果导出与写入在同一脚本中,则可能在 Paper 渲染它们之前执行,导致 PNG 显示缺失或过时的内容。
  • export 写入到 ~/Downloads/<artboard name>.png,其他比例带 @2x 或 @0.5x 后缀,当存在同名文件时添加 (1)、 (2) 后缀。像 ? 和 : 这样的字符会变成 _。始终使用返回的 filePath。不要使用 ls -t ~/Downloads 或名称通配符选择文件:那可能从早期运行中获取过时的导出。
  • 读取后移动或删除导出,以免它们堆积在 ~/Downloads 中。不要动未创建的文件。
  • 对于审查截图,使用 0.5x 以节省令牌;对于交付物,使用 2x(1920×1080 的画板变成 3840×2160)。
  • export_combined_pdf 按画布位置顺序将画板合并为一个 PDF。export 也支持 mp4/webm 视频,以及用于 SVG 节点的 svg、jpg、webp、avif 和 pdf。
  • 如需将画板转换为 Google Slides,请参见 references/slides-export.md。

编写 HTML

write_html 工具描述列出了规则(内联样式、flex、无 margin、无 display: grid、无表格、使用 layer-name 作为图层名称)。额外发现:

  • 空的 <div> 会变成矩形节点,即使有 display: flex,并且矩形不能有子节点。向其中写入会失败并提示“cannot have children”。请将容器及其第一个子节点一起写入,或者在 replace 模式下使用 write_html 替换矩形。
  • 不支持富文本。对于语法高亮的代码或包含一个彩色单词的句子,构建一行 flex 行,每个令牌一个文本节点,每个节点设置 white-space: pre。代码变成每行一个 flex 行。使用辅助函数生成 HTML,而不是手动编写。
  • 带有渐变图层的 background 会忽略 backgroundColor。将基础颜色添加为最后一层:linear-gradient(#F6F7F9, #F6F7F9)。
  • 内联 <svg> 适用于图标、标志和连接线,并接受 fill/stroke 颜色和令牌变量。内联品牌标志的 SVG 而不是链接到它。
  • SVG 路径填充对 find_nodes 和 get_computed_styles(对它们返回 {})不可见。在 get_tree_summary 中找到 SVGVisualElement "Path" 节点,并使用 update_styles({ styles: { fill } }) 重新着色,这确实有效。
  • 本地图像需要使用绝对路径和 paper-asset:// 方案:<img src="paper-asset:///private/tmp/spec/foo.png">。
  • 在 flex 行中等宽列需要 flex: 1; flex-basis: 0。否则,某一列上的内边距会使其比兄弟列更宽。
  • 如果内容被裁剪,使用 update_styles 将画板设置为 height: "fit-content",而不是猜测新的高度。

字体

  • get_font_family_info({ fileId, familyNames: [...] }) 检查机器上安装的字体和 Google Fonts。在会话中首次排版样式设置前调用它。
  • 会话期间安装的字体(例如,在 macOS 上安装到 ~/Library/Fonts)直到 Paper 重启才会显示。请用户退出并重新打开 Paper。不要自己终止 Paper,因为这可能导致状态丢失。
  • 在将字体用于公共网站之前检查其许可证。许多 dafont.com 的字体仅限个人使用免费。
  • 要比较多种字体而无需重启 Paper,可以将标本本地渲染为透明 PNG(例如,使用 Pillow),将它们作为 paper-asset:// 图像放置在标本画板上,然后只安装最终候选字体。

组织画布

  • 在对设计进行迭代时,复制画板并修改副本。不要原地编辑较旧的画板:用户希望看到进展,并且经常要求回到早期版本。一位用户不得不在一个会话中要求两次才得到满足。
  • create_artboard 和 duplicate_nodes 会将新画板放在最佳空白位置,这会导致它们分散。要排列它们,请使用 update_styles 设置画板的 top 和 left。画板之间保持至少 80px 的间距。
  • 将设计探索与选定方向分开:放在画布自己的区域中,或放在自己的页面中(create_page,然后传递 pageId)。无法切换用户正在查看的页面。
  • 对于幻灯片和变体集,使用固定网格(例如,每行四个,x = x0 + col × (width + gap))。当添加或移除画板时,重新排列其余画板使其无缝对齐,并重新命名。
  • 对于浅色和深色变体,将浅色行放在顶部,并将匹配的深色版本直接放在每个下方。要制作深色版本,请参见 references/dark-mode-variants.md。
  • 为画板指定描述性名称,如 Homepage · A (Editorial) 或 04 · What are Workers?。导出文件名来源于它们。
  • 节点 ID 不会在会话结束后保留。将文件 ID、URL、命名方案、网格公式和样式规则写入项目的 AGENTS.md,以便下一个会话可以继续。

设计转代码

  • 使用 get_jsx(format: "inline-styles" 或 "tailwind")和 get_computed_styles 读取精确值。切勿从截图中测量大小或颜色。
  • 计算后的颜色可能以 oklab(...) 形式返回。直接复制它们而不是转换。
  • 构建页面后,在真实浏览器中与 Paper 导出进行比较,并检查浅色和深色模式。

MCP 无法执行的操作

  • 共享文件或管理权限。用户从 Paper 应用共享:邀请团队成员编辑,或发送文件 URL 用于只读访问。
  • 将文件移动到文件夹中。从 create_file 新建的文件位于用户文件夹之外,用户必须手动拖入。
  • 在文件之间移动节点。move_nodes 仅在同一文件内跨页面移动。要移动整个文件的内容,请使用 create_file({ cloneFileId, name }),检查节点和画板数量是否匹配,然后 delete_nodes 删除原始文件。删除前请与用户确认。
  • 切换用户正在查看的页面。

相关技能