Communitygithub.com

zeke/paper-app-skill

在 Paper 設計應用程式 (paper.design) 中透過其本地 MCP 伺服器 (mcp__paper 工具) 進行操作。當被要求進行設計、製作模型、探索變體、編輯畫板或幻燈片、擷取螢幕截圖或匯出 PNG、PDF 或影片、移動或複製 Paper 檔案、使用自訂字型或本地圖片,或將 Paper 設計轉換為程式碼時使用。涵蓋 codemode 怪癖、結果解析、匯出與螢幕截圖的替代方案、畫布組織以及 MCP 無法做到的事項。

paper-app-skill 是什麼?

這個技能 (skill) 將 Paper 設計工具 (paper.design) 的本地 MCP 伺服器整合到 AI 代理程式的流程中,讓代理程式可以讀取畫布並將 HTML 直接寫入其中作為設計節點。主要支援在 Codemode 環境 (如 Claude Code) 中使用,提供了一系列關鍵的常見問題解決方案和最佳實踐,這些內容是官方 MCP 指南中未提及的,包括如何處理平行呼叫失敗、正確解析工具結果、繞過螢幕截圖限制、有效組織畫布以及管理字型。該技能強調必須按序列執行相依的 Paper 呼叫,匯出動作必須與編輯動作分開進行,並利用提供的輔助函式 (helper functions) 來處理常見任務。它還明確說明了 MCP 伺服器的限制,例如無法管理檔案權限、無法在資料夾之間移動檔案,以及節點 ID 在會話之間無法持久保存。

相容平台~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. 檢查工具是否存在:在 codemode 中執行 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 和畫布慣例。

從 codemode 呼叫 Paper

  • 在單一腳本中使用 await 或按序輪次執行相依的 Paper 呼叫。絕對不要在同一輪次中將它們作為平行工具呼叫發出。平行呼叫是造成過去工作階段中大多數失敗的原因,而且每次都被誤讀為 Paper 的錯誤:例如「找不到節點」(針對剛由同級呼叫建立的節點)、子節點以亂序排列、匯出遺漏最近的寫入內容,以及 read 因 ENOENT 錯誤失敗 (針對尚未寫入的匯出內容)。
  • 每個檔案範圍的工具都需要在 codemode 宣告中包含 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 傳遞會導致「找不到節點」錯誤。
  • 檔案範圍的失敗和驗證錯誤會拋出例外。某些非檔案範圍的失敗 (例如「請開啟一個 Paper 檔案以使用此工具。」) 則會以 isError: true 來解決。不要將 Paper 呼叫包在 .catch(() => {}) 中:否則錯誤的參數型狀會悄悄地失敗,而編輯動作永遠不會顯示。
  • 「您的 Paper 檔案目前已與伺服器中斷連線」表示寫入會失敗,但讀取仍可運作。在過去的工作階段中,等待並重試並未解決此問題。請要求使用者將 Paper 檔案帶回前景,然後重試。
  • get_screenshot 在 codemode 中不會回傳圖片:其結果會被扁平化為標頭字串。請使用下方的 shot() 輔助函式,它會在同一個腳本中匯出 PNG 並讀取它。當測試工具直接呼叫 MCP 工具 (而非透過 codemode) 時,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。請檢查它。

輔助函式

將這些內容貼到 codemode 腳本的頂端,或儲存一次並在後續呼叫中 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() 僅在腳本成功時才會保留值,因此請在一個不做其他事情的腳本中儲存輔助函式。

螢幕截圖與匯出

  • 在與編輯動作不同的 codemode 呼叫中進行螢幕截圖。如果匯出與寫入動作在同一個腳本中,它可能會在 Paper 渲染之前執行,導致 PNG 顯示遺失或過時的內容。
  • export 會寫入到 ~/Downloads/<artboard name>.png,對於其他比例會加上 @2x 或 @0.5x,而當具有該名稱的檔案已存在時,則會加上 (1)、 (2) 等後綴。? 和 : 等字元會變成 _。始終使用回傳的 filePath。不要使用 ls -t ~/Downloads 或名稱萬用字元來挑選檔案:這可能會選到來自較早執行的過期匯出。
  • 在讀取匯出檔案後移動或刪除它們,以免它們堆積在 ~/Downloads 中。不要動您未建立的檔案。
  • 使用 0.5x 進行審查螢幕截圖以節省 token,使用 2x 作為交付成果 (1920×1080 的畫板會變成 3840×2160)。
  • export_combined_pdf 會將多個畫板合併成一個 PDF,並按畫布位置排序。export 也支援 mp4/webm 影片,以及 SVG 節點的 svg 格式、jpg、webp、avif 和 pdf 格式。
  • 若要將畫板轉換為 Google 簡報,請參閱 references/slides-export.md。

撰寫 HTML

write_html 工具說明列出了規則 (行內樣式、flex、無 margin、無 display: grid、無表格、使用 layer-name 作為圖層名稱)。其他發現:

  • 空的 <div> 會變成矩形節點,即使有 display: flex 也是如此,而矩形不能有子節點。寫入其中會失敗,並出現「無法擁有子節點」的錯誤。請將容器連同其第一個子節點一起寫入,或者使用 replace 模式的 write_html 來取代矩形。
  • 不支援富文字。對於語法高亮的程式碼或包含一個彩色詞語的句子,請建立一個由獨立文字節點組成的 flex 行,每個 token 一個節點,每個節點都設定 white-space: pre。程式碼則變成每行一個 row。使用輔助函式來產生 HTML,而不是手動撰寫。
  • 帶有漸層圖層的 background 會忽略 backgroundColor。請將基底顏色作為最後一個圖層加入:linear-gradient(#F6F7F9, #F6F7F9)。
  • 行內 <svg> 適用於圖示、標誌和連線線,並且可以接受 fill/stroke 顏色和 token 變數。請直接使用品牌標誌的 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。否則,其中一個欄位的內距 (padding) 會使其比同層級的其他欄位更寬。
  • 如果內容被裁剪,請使用 update_styles 將畫板設為 height: "fit-content",而不是猜測一個新的高度。

字型

  • get_font_family_info({ fileId, familyNames: [...] }) 會檢查機器上安裝的字型以及 Google Fonts。請在工作階段中進行第一個排版樣式設定之前呼叫它。
  • 在工作階段進行中安裝的字型 (例如,在 macOS 上安裝到 ~/Library/Fonts) 在重新啟動 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。
  • 為畫板賦予描述性名稱,例如 首頁 · A (編輯) 或 04 · 什麼是 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 刪除原始檔案。在刪除前請先與使用者確認。
  • 切換使用者正在查看的頁面。

相關技能