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호출당 하나의 시각적 그룹을 작성하고, 스크린샷으로 검토하고, 완료되면finish_working_on_nodes를 호출합니다. 기타 주제:mobile-status-bar,figma-import,image-generation. - 파일을 찾습니다.
list_resources({ limit: 200 })는 파일을 열지 않고도 나열합니다. 모든 파일 범위 호출에fileId를 전달하세요.get_basic_info({ fileId })는 포그라운드 탭이 아닌 파일에서도 작동하므로open_file이 거의 필요하지 않습니다.open_file은 사용자 앱에서 탭을 엽니다. - 프로젝트에
AGENTS.md가 있으면 탐색 전에 Paper 파일 ID와 캔버스 규칙을 확인하세요.
코더모드에서 Paper 호출하기
- 종속된 Paper 호출은 하나의 스크립트에서
await를 사용하거나 순차적인 턴으로 실행하세요. 같은 턴에서 병렬 도구 호출로 절대 실행하지 마세요. 과거 세션에서 병렬 호출이 대부분의 실패 원인이었으며, 각각 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"로 실패합니다. - 파일 범위 실패 및 유효성 검사 오류는 예외를 발생시킵니다. 일부 비파일 실패(예: "Open a Paper file to use this tool.")는 대신
isError: true로 해결됩니다. Paper 호출을.catch(() => {})로 감싸지 마세요: 잘못된 인자 형태가 조용히 실패하고 편집 내용이 나타나지 않습니다. - "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"))를 실행하세요. const와 let은 eval 내부에서 외부로 유출되지 않으므로 var로 헬퍼를 선언하세요. 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는 캔버스 위치 순서로 아트보드를 하나의 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가 있어도 Rectangle 노드가 되며, Rectangle은 자식을 가질 수 없습니다. 여기에 쓰기를 시도하면 "cannot have children" 오류가 발생합니다. 컨테이너와 첫 번째 자식을 함께 쓰거나,replace모드의write_html로 Rectangle을 교체하세요. - 리치 텍스트는 지원되지 않습니다. 구문 강조된 코드나 하나의 색상이 있는 문장의 경우, 토큰당 하나씩, 각각
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를 종료하고 다시 열도록 요청하세요. 상태가 손실될 수 있으므로 직접 종료하지 마세요. - 공개 사이트에 폰트를 올리기 전에 라이선스를 확인하세요. 많은 dafont.com 폰트는 개인 용도로만 무료입니다.
- Paper를 다시 시작하지 않고 여러 폰트를 비교하려면 로컬에서 투명 PNG로 견본을 렌더링하고(예: Pillow 사용), 표본 아트보드에
paper-asset://이미지로 배치한 다음 최종 후보만 설치하세요.
캔버스 구성
- 디자인을 반복할 때는 아트보드를 복제하고 복사본을 변경하세요. 이전 보드를 제자리에서 편집하지 마세요: 사용자는 진행 상황을 보고 싶어하며 종종 이전 버전으로 돌아가기를 요청합니다. 한 사용자는 세션에서 이 내용을 두 번 요청해야 했습니다.
create_artboard와duplicate_nodes는 새 아트보드를 최적의 빈 공간에 배치하여 흩어지게 합니다. 배열하려면update_styles로 아트보드의top과left를 설정하세요. 아트보드 사이에 최소 80px를 유지하세요.- 디자인 탐색을 선택한 방향과 분리하여 유지하세요: 캔버스의 자체 영역이나 자체 페이지에 두세요(
create_page를 사용한 후pageId전달). 사용자가 보고 있는 페이지를 전환할 수 없습니다. - 데크 및 변형 세트의 경우 고정 그리드를 사용하세요(예: 행당 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로 원본을 삭제하세요. 삭제하기 전에 사용자에게 확인하세요. - 사용자가 보고 있는 페이지 전환.
