Communitygithub.com

zeke/paper-app-skill

Paper 디자인 앱(paper.design)의 로컬 MCP 서버를 통해 작업합니다. 디자인, 목업, 변형 탐색, 아트보드나 슬라이드 편집, 스크린샷 및 PNG/PDF/비디오 내보내기, 파일 이동/복제, 커스텀 폰트 및 로컬 이미지 사용, Paper 디자인을 코드로 변환할 때 사용합니다.

paper-app-skill란 무엇인가요?

이 스킬은 Paper 디자인 앱의 MCP 서버(mcp__paper)를 통해 AI 에이전트가 Paper 캔버스를 읽고 HTML을 디자인 노드로 작성할 수 있게 해줍니다. Paper는 실제 HTML과 CSS를 기반으로 하는 디자인 도구로, 데스크톱 앱이 로컬 MCP 서버를 실행하여 에이전트와 통신합니다. 이 스킬은 공식 가이드에서 다루지 않는 실제 세션 경험을 바탕으로 한 필수 정보를 제공합니다: 코더모드 호출 규칙(병렬 호출 금지, fileId 필수), 스크린샷 및 내보내기 작업 흐름(쓰기와 분리, @0.5x/@2x 사용), HTML 작성 시 주의사항(빈 div는 Rectangle이 되어 자식을 가질 수 없음, 리치 텍스트 미지원, inline SVG 지원), 폰트 관리, 캔버스 구성(아트보드 복제 후 수정, 그리드 배치, 파일 간 노드 이동 불가) 등을 상세히 설명합니다. 또한 디자인을 코드로 변환하는 방법과 MCP가 할 수 없는 작업(파일 공유, 폴더 이동, 사용자 페이지 전환)도 명시합니다.

지원 대상~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/zeke/paper-app-skill/tree/HEAD/skills/paper-app

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

미리보기

스킬 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에서 실행)는 서버 자체의 상태를 보여줍니다. 서버가 정상이면 세션이 스스로 다시 연결할 수 없으므로, 사용자에게 pi에서 /mcp reconnect paper를 실행하거나, 그래도 안 되면 /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이 거의 필요하지 않습니다. open_file은 사용자 앱에서 탭을 엽니다.
  4. 프로젝트에 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로 원본을 삭제하세요. 삭제하기 전에 사용자에게 확인하세요.
  • 사용자가 보고 있는 페이지 전환.

관련 스킬