Paperアプリ
Paperは、キャンバスが実際のHTMLとCSSで構成されたデザインツールです。PaperデスクトップアプリはローカルMCPサーバーを実行し、エージェントがキャンバスを読み取り、その中にデザインノードとしてHTMLを書き込むことを可能にします。
サーバーには独自のガイドが付属しています。このスキルは、そのガイドで省略されている部分、つまり実際のセッションを通じて苦労して学んだことをカバーします。
始める前に
- ツールが存在することを確認します。コードモードで
await describeNamespace("mcp__paper")を実行します。名前空間が空の場合、またはALL_TOOLSにmcp__paper__*エントリがない場合、セッションは接続されていません。通常、セッション開始時にPaperが開いていなかったためです。サーバーはhttp://127.0.0.1:29979/mcpにあり、Paperが開いている間だけ応答します。pi mcp list(bashから実行)は、サーバー自体が正常かどうかを示します。正常な場合、セッションは自動的に再接続できません。ユーザーにpiで/mcp reconnect paperを実行するか、それが機能しない場合は/reloadを実行するよう依頼してください。 - セッションごとに一度公式ガイドを読み込みます。
get_guide({ topic: "paper-mcp-instructions" })を実行します。それに従います。新しいデザインの前にデザインブリーフを投稿し、write_htmlの呼び出しごとに1つのビジュアルグループを書き込み、スクリーンショットで確認し、完了したらfinish_working_on_nodesを呼び出します。その他のトピック:mobile-status-bar、figma-import、image-generation。 - ファイルを見つけます。
list_resources({ limit: 200 })は、ファイルを開いていなくてもファイルを一覧表示します。すべてのファイルスコープの呼び出しにfileIdを渡します。get_basic_info({ fileId })はフォアグラウンドタブではないファイルでも機能するため、ユーザーのアプリでタブを開くopen_fileが必要になることはほとんどありません。 - プロジェクトに
AGENTS.mdがある場合は、探索する前にPaperファイルIDとキャンバス規則を確認します。
コードモードからPaperを呼び出す
- 依存関係のあるPaperの呼び出しは、1つのスクリプト内で
awaitを使用して、または順次ターンで実行します。同じターン内で並行ツール呼び出しとして発行しないでください。並行呼び出しは過去のセッションでほとんどの失敗の原因となっており、それぞれがPaperのバグと誤解されていました。兄弟呼び出しが作成したばかりのノードが見つからない、子ノードがスクランブルされた順序で配置される、最近の書き込みが欠落したエクスポート、まだ書き込まれていないエクスポートでreadがENOENTで失敗するなどです。 - すべてのファイルスコープツールは、サーバーの指示では省略可能とされていますが、コードモード宣言に
fileIdが必要です。省略すると検証に失敗します。fileIdを受け付けないのはget_basic_infoとget_selectionのみです。 - ツールを最初に使用する前に
describeTool("mcp__paper__<name>")を実行します。引数の形状は推測できません。get_font_family_infoはfamilyNames: string[]を取り、set_text_contentとupdate_stylesはupdates配列を取り、exportはnodesマップを取ります。 - ファイルスコープのツールは1つのプレーン文字列を返します。
{ file, contentHash }ヘッダーJSONブロックと、それに続く本文です。ファイルがないツール(list_resources、create_file、open_file、get_guide)は{ content: [{ type: "text", text }] }を返します。結果から最初の"id"を正規表現で抽出しないでください。それはファイルIDであり、ノードIDとして渡すと「Node not found」で失敗します。 - ファイルスコープの失敗と検証エラーはスローされます。一部の非ファイルの失敗(「このツールを使用するにはPaperファイルを開いてください。」など)は、代わりに
isError: trueで解決します。Paperの呼び出しを.catch(() => {})でラップしないでください。間違った引数形状がサイレントに失敗し、編集が表示されなくなります。 - 「Paperファイルが現在サーバーから切断されています」は、読み取りは機能するが書き込みが失敗することを意味します。過去のセッションでは、スリープと再試行では修正されませんでした。ユーザーに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 で宣言します。const と let は eval 内から漏れ出さないためです。store() はスクリプトが成功した場合にのみ値を保持するため、ヘルパーは他に何もしないスクリプトで保存します。
スクリーンショットと書き出し
- スクリーンショットは編集とは別のコードモード呼び出しで行います。書き込みと同じスクリプト内でのエクスポートは、Paperがそれらをレンダリングする前に実行される可能性があり、PNGにコンテンツが欠落または古くなる可能性があります。
exportは~/Downloads/<アートボード名>.pngに書き出します。他のスケールの場合は@2xまたは@0.5xが付き、同じ名前のファイルが存在する場合は(1)、(2)のサフィックスが付きます。?や:などの文字は_になります。常に返されたfilePathを使用してください。ls -t ~/Downloadsや名前のグロブでファイルを選択しないでください。以前の実行からの古いエクスポートを取得する可能性があります。- 読み取り後はエクスポートを移動または削除して、
~/Downloadsに溜まらないようにします。自分で作成していないファイルはそのままにします。 - 確認用のスクリーンショットには
0.5xを使用してトークンを節約し、成果物には2x(1920×1080のアートボードは3840×2160になります)を使用します。 export_combined_pdfは、キャンバス上の位置順にアートボードを1つのPDFに結合します。exportはmp4/webmビデオ、SVGノードの場合はsvg、jpg、webp、avif、pdfも行います。- アートボードをGoogleスライドに変換するには、references/slides-export.md を参照してください。
HTMLの書き込み
write_html ツールの説明にはルール(インラインスタイル、フレックスボックス、margin なし、display: grid なし、テーブルなし、レイヤー名の layer-name)がリストされています。追加の知見:
- 空の
<div>は、display: flexでもRectangleノードになり、Rectangleは子を持つことができません。そこに書き込むと「cannot have children」で失敗します。コンテナとその最初の子を一緒に書き込むか、write_htmlをreplaceモードで使用してRectangleを置き換えます。 - リッチテキストはサポートされていません。シンタックスハイライトされたコードや、1つの単語だけ色が異なる文の場合は、トークンごとに個別のテキストノードのフレックス行を作成し、それぞれに
white-space: preを設定します。コードは1行につき1行になります。手動で記述するのではなく、ヘルパーを使用して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: 1; flex-basis: 0が必要です。そうしないと、1つの列にパディングがあると兄弟列よりも幅が広くなります。 - コンテンツがクリップされる場合は、新しい高さを推測する代わりに、
update_stylesでアートボードをheight: "fit-content"に設定します。
フォント
get_font_family_info({ fileId, familyNames: [...] })は、マシンにインストールされているフォントとGoogleフォントをチェックします。セッションでの最初のタイポグラフィスタイリングの前に呼び出します。- セッションの途中(macOSで
~/Library/Fontsにインストールするなど)でインストールされたフォントは、Paperを再起動するまで表示されません。ユーザーにPaperを終了して再度開くよう依頼します。自分で強制終了しないでください。ユーザーの状態が失われる可能性があります。 - 公開サイトで使用する前に、フォントのライセンスを確認してください。dafont.com の多くのフォントは個人使用のみ無料です。
- Paperを再起動せずに多くのフォントを比較するには、ローカルで(Pillowなどを使用して)透明PNGに見本をレンダリングし、
paper-asset://画像として見本アートボードに配置し、最終候補のみをインストールします。
キャンバスの整理
- デザインを反復するときは、アートボードを複製してコピーを変更します。以前のボードをその場で編集しないでください。ユーザーは進行状況を確認したいと考えており、以前のバージョンに戻すように依頼することがよくあります。あるユーザーはセッションでこれを2回依頼しなければなりませんでした。
create_artboardとduplicate_nodesは、新しいアートボードを最適な空のスポットにドロップするため、散在します。これらを整理するには、update_stylesでアートボードにtopとleftを設定します。アートボード間は少なくとも80pxの間隔を保ちます。- デザインの探索は、選択した方向とは別に、キャンバスの独自の領域、または独自のページ(
create_pageを実行し、pageIdを渡す)に保管します。ユーザーが表示しているページを切り替えることはできません。 - デッキやバリアントセットには、固定グリッド(例:1行あたり4つ、
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で元のノードを削除します。削除する前にユーザーに確認します。 - ユーザーが見ているページの切り替え。
