Paper app
Paper é uma ferramenta de design cuja tela é HTML e CSS reais. O aplicativo de desktop Paper executa um servidor MCP local que permite que agentes leiam a tela e escrevam HTML nela como nós de design.
O servidor vem com seu próprio guia. Esta skill cobre o que esse guia omite: coisas aprendidas da maneira mais difícil ao longo de sessões reais.
Antes de começar
- Verifique se as ferramentas existem:
await describeNamespace("mcp__paper")no codemode. Se o namespace estiver vazio ouALL_TOOLSnão tiver entradasmcp__paper__*, a sessão não se conectou, geralmente porque o Paper não estava aberto quando a sessão começou. O servidor está emhttp://127.0.0.1:29979/mcpe só responde enquanto o Paper estiver aberto.pi mcp list(execute no bash) mostra se o servidor em si está saudável. Se estiver, a sessão não pode se reconectar sozinha: peça ao usuário para executar/mcp reconnect paperno pi, ou/reloadse isso não funcionar. - Carregue o guia oficial uma vez por sessão:
get_guide({ topic: "paper-mcp-instructions" }). Siga-o: publique um briefing de design antes de novos designs, escreva um grupo visual por chamadawrite_html, revise com screenshots e chamefinish_working_on_nodesquando terminar. Outros tópicos:mobile-status-bar,figma-import,image-generation. - Encontre o arquivo.
list_resources({ limit: 200 })lista arquivos sem que um esteja aberto. PassefileIdpara toda chamada com escopo de arquivo.get_basic_info({ fileId })funciona em um arquivo que não é a aba em primeiro plano, então raramente você precisa deopen_file, que abre uma aba no aplicativo do usuário. - Se o projeto tiver um
AGENTS.md, procure o ID do arquivo Paper e as convenções de tela lá antes de explorar.
Chamando o Paper a partir do codemode
- Execute chamadas dependentes do Paper em um único script com
await, ou em turnos sequenciais. Nunca as emita como chamadas de ferramenta paralelas no mesmo turno. Chamadas paralelas causaram a maioria das falhas em sessões passadas, e cada uma foi mal interpretada como um bug do Paper: "Node not found" para um nó que uma chamada irmã acabara de criar, filhos chegando em ordem embaralhada, exportações perdendo escritas recentes ereadfalhando com ENOENT em uma exportação que ainda não havia sido escrita. - Toda ferramenta com escopo de arquivo requer
fileIdem sua declaração codemode, mesmo que as instruções do servidor digam que você pode omiti-lo. Omiti-lo falha na validação. Apenasget_basic_infoeget_selectionaceitam nenhumfileId. - Execute
describeTool("mcp__paper__<name>")antes do primeiro uso de uma ferramenta. As formas dos argumentos não são adivinháveis:get_font_family_inforecebefamilyNames: string[],set_text_contenteupdate_stylesrecebem um arrayupdates, eexportrecebe um mapanodes. - Ferramentas com escopo de arquivo retornam uma string simples: um bloco JSON de cabeçalho
{ file, contentHash }, depois o corpo. Ferramentas sem arquivo (list_resources,create_file,open_file,get_guide) retornam{ content: [{ type: "text", text }] }. Não use regex para extrair o primeiro"id"de um resultado: é o ID do arquivo, e passá-lo como ID de nó falha com "Node not found". - Falhas com escopo de arquivo e erros de validação lançam exceções. Algumas falhas sem arquivo (como "Open a Paper file to use this tool.") resolvem com
isError: trueem vez disso. Não envolva chamadas do Paper em.catch(() => {}): uma forma de argumento errada então falha silenciosamente e a edição simplesmente nunca aparece. - "Your Paper file is currently disconnected from the server" significa que as escritas falham enquanto as leituras ainda funcionam. Dormir e repetir não resolveu em sessões passadas. Peça ao usuário para trazer o arquivo Paper de volta ao primeiro plano e repita.
get_screenshotnão retorna imagem no codemode: seu resultado é achatado para a string de cabeçalho. Use o helpershot()abaixo, que exporta um PNG e o lê no mesmo script. Quando um harness chama ferramentas MCP diretamente em vez de através do codemode, as imagens deget_screenshotvêm.
Formas úteis de resultado, após analisar o corpo:
| Ferramenta | Corpo |
|---|---|
create_artboard | { id, name, worldX, worldY, width, height } |
write_html | { createdNodes: [{ id, name, component }] }, apenas nós de nível superior |
get_tree_summary | { summary }, uma árvore de texto indentada com 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 | Lista ignoredStyles quando um estilo é inerte naquele contexto. Verifique-o. |
Helpers
Cole estes no topo de um script codemode, ou armazene-os uma vez e eval eles em chamadas posteriores:
var F = "<fileId>";
var txt = r => typeof r === "string" ? r : (r?.content ?? []).map(c => c.text ?? "").join("\n");
// Remove o cabeçalho { file, contentHash } que ferramentas com escopo de arquivo prefixam.
var body = r => { const t = txt(r); const i = t.indexOf("\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;
// Exporta um nó para PNG, mostra-o e então exclui o arquivo de ~/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 });
};
Para reutilizar helpers entre chamadas: store("paper", helperSource), depois eval(load("paper")) no topo de cada script posterior. Declare helpers com var, porque const e let dentro de eval não vazam para fora. store() só mantém valores quando o script é bem-sucedido, então armazene helpers em um script que não faz mais nada.
Screenshots e exportações
- Tire screenshots em uma chamada codemode separada das edições. Uma exportação no mesmo script das escritas pode ser executada antes que o Paper as renderize, então o PNG mostra conteúdo ausente ou desatualizado.
exportescreve em~/Downloads/<nome da prancheta>.png, com@2xou@0.5xpara outras escalas e sufixos(1),(2)quando um arquivo com esse nome já existe. Caracteres como?e:tornam-se_. Sempre use ofilePathretornado. Não selecione arquivos comls -t ~/Downloadsou globs de nome: isso já pegou exportações desatualizadas de execuções anteriores.- Mova ou exclua exportações após lê-las para que não se acumulem em
~/Downloads. Deixe arquivos que você não criou em paz. - Use
0.5xpara screenshots de revisão para economizar tokens,2xpara entregáveis (pranchetas 1920×1080 tornam-se 3840×2160). export_combined_pdfmescla pranchetas em um único PDF, ordenado por posição na tela.exporttambém faz vídeomp4/webm, maissvgpara nós SVG,jpg,webp,avifepdf.- Para transformar pranchetas em Google Slides, veja references/slides-export.md.
Escrevendo HTML
A descrição da ferramenta write_html lista as regras (estilos inline, flex, sem margin, sem display: grid, sem tabelas, layer-name para nomes de camadas). Descobertas adicionais:
- Uma
<div>vazia se torna um nó Rectangle, mesmo comdisplay: flex, e Rectangles não podem ter filhos. Escrever dentro de um falha com "cannot have children". Escreva um contêiner junto com seu primeiro filho, ou substitua o Rectangle comwrite_htmlno modoreplace. - Texto rico não é suportado. Para código com destaque de sintaxe ou uma frase com uma palavra colorida, construa uma linha flex de nós de texto separados, um por token, cada um com
white-space: pre. O código se torna uma linha por linha de código. Gere o HTML com um helper em vez de escrevê-lo manualmente. - Um
backgroundcom camadas de gradiente ignorabackgroundColor. Adicione a cor base como a última camada:linear-gradient(#F6F7F9, #F6F7F9). <svg>inline funciona para ícones, logotipos e linhas de conexão, e aceita coresfill/strokee variáveis de token. Coloque o SVG de um logotipo de marca inline em vez de vinculá-lo.- Preenchimentos de caminho SVG são invisíveis para
find_nodeseget_computed_styles(que retorna{}para eles). Encontre os nósSVGVisualElement "Path"emget_tree_summarye recolorir-os comupdate_styles({ styles: { fill } }), que funciona. - Imagens locais precisam de um caminho absoluto com o esquema
paper-asset://:<img src="paper-asset:///private/tmp/spec/foo.png">. - Colunas de largura igual em uma linha flex precisam de
flex: 1; flex-basis: 0. Padding em uma coluna caso contrário a torna mais larga que suas irmãs. - Se o conteúdo cortar, defina a prancheta para
height: "fit-content"comupdate_stylesem vez de adivinhar uma nova altura.
Fontes
get_font_family_info({ fileId, familyNames: [...] })verifica fontes instaladas na máquina e Google Fonts. Chame-o antes do primeiro estilo tipográfico em uma sessão.- Fontes instaladas no meio da sessão (por exemplo, em
~/Library/Fontsno macOS) não aparecem até que o Paper seja reiniciado. Peça ao usuário para sair e reabrir o Paper. Não o mate você mesmo, pois isso pode perder o estado deles. - Verifique a licença de uma fonte antes de colocá-la em um site público. Muitas fontes do dafont.com são gratuitas apenas para uso pessoal.
- Para comparar muitas fontes sem reiniciar o Paper, renderize espécimes em PNGs transparentes localmente (por exemplo, com Pillow), coloque-os como imagens
paper-asset://em uma prancheta de espécime e instale apenas os finalistas.
Organizando a tela
- Ao iterar em um design, duplique a prancheta e altere a cópia. Não edite pranchetas anteriores no lugar: os usuários querem ver a progressão e frequentemente pedem para voltar a uma versão anterior. Um usuário teve que pedir isso duas vezes em uma sessão.
create_artboardeduplicate_nodescolocam novas pranchetas no melhor espaço vazio, o que as espalha. Para organizá-las, definatopeleftnas pranchetas comupdate_styles. Mantenha pelo menos 80px entre pranchetas.- Mantenha as explorações de design separadas da direção escolhida: em sua própria região da tela, ou em sua própria página (
create_page, depois passepageId). Você não pode alternar qual página o usuário está visualizando. - Para decks e conjuntos de variantes, use uma grade fixa (por exemplo, quatro por linha,
x = x0 + col × (width + gap)). Quando uma prancheta é adicionada ou removida, reorganize o resto para que não haja lacunas e renumerar seus nomes. - Para variantes claras e escuras, coloque a linha clara no topo e a versão escura correspondente diretamente abaixo de cada uma. Para fazer as versões escuras, veja references/dark-mode-variants.md.
- Dê às pranchetas nomes descritivos como
Homepage · A (Editorial)ou04 · What are Workers?. Os nomes dos arquivos exportados vêm deles. - IDs de nós não sobrevivem à sessão. Escreva o ID do arquivo, URL, esquema de nomenclatura, fórmula de grade e regras de estilo no
AGENTS.mddo projeto para que a próxima sessão possa retomar.
Design para código
- Leia valores exatos com
get_jsx(format: "inline-styles"ou"tailwind") eget_computed_styles. Nunca meça tamanhos ou cores de um screenshot. - Cores computadas podem vir como
oklab(...). Copie-as literalmente em vez de convertê-las. - Após construir a página, compare-a em um navegador real com uma exportação do Paper, nos modos claro e escuro.
O que o MCP não pode fazer
- Compartilhar arquivos ou gerenciar permissões. O usuário compartilha do aplicativo Paper: convide pessoas para a equipe para permitir que editem, ou envie a URL do arquivo para acesso somente leitura.
- Mover arquivos para pastas. Novos arquivos de
create_filecaem fora das pastas do usuário, e o usuário tem que arrastá-los para dentro. - Mover nós entre arquivos.
move_nodessó move dentro de um arquivo, entre páginas. Para mover o conteúdo de um arquivo inteiro,create_file({ cloneFileId, name }), verifique se as contagens de nós e pranchetas correspondem, depoisdelete_nodesos originais. Confirme com o usuário antes de excluir. - Alternar a página que o usuário está visualizando.
