Modelo de contexto para design: como o GitHub Copilot usa o Figma MCP para não inventar pixel - Parte 2

Modelo de contexto para design: como o GitHub Copilot usa o Figma MCP para não inventar pixel - Parte 2

10 de setembro de 2026

O Model Context Protocol é o padrão aberto que permite a uma ferramenta de IA como o GitHub Copilot se conectar a fontes externas de dados através de servidores dedicados, em vez de depender só do que está escrito no código ou do que o modelo já sabe de treinamento. No projeto BookStore, dois desses servidores MCP foram plugados ao GitHub Copilot: um para o PrimeVue, que expõe a documentação de cada componente diretamente no chat, e um para o Figma, que permite ler um frame real de design e extrair medidas, cores e textos exatos. Este artigo é a Parte 2 da série sobre a modernização do front-end do BookStore, e o foco aqui é justamente esse segundo servidor: como o GitHub Copilot usa o Figma MCP para implementar um componente Vue sem chutar um valor sequer.

Tela Home:

Tela Books:

Tela Authors:


O motivo de isso importar é simples. Numa tarefa de UI, é fácil pedir para uma IA "fazer parecido com essa imagem" e receber de volta um componente com espaçamento aproximado, cor levemente errada e tipografia no tamanho padrão do framework. Isso funciona para um protótipo, mas não para um sistema de design real, onde 21px de border-radius e 67px de altura de cabeçalho são valores deliberados, não arredondamentos. Por isso o repositório tem uma skill dedicada só para essa etapa: a figma-discovery, que roda antes de qualquer linha de componente ser escrita.

Skill: figma-discovery

---
name: figma-discovery
description: "Discovers and extracts design specs from Figma for a Vue/PrimeVue front-end. Use when: the user gives a Figma URL or node reference, asks to implement/match a design, refactor existing UI to align with the design system, or a new/changed component needs accurate spacing, color, typography or assets from Figma before coding. Parses Figma URLs, calls the Figma MCP tools, and maps raw values to the codebase's existing PrimeVue/Tailwind design-token conventions instead of raw hex/pixel values."
argument-hint: "A Figma URL or node reference, and what component/page it's for"
---

# Figma Discovery

Extracts an implementable spec from a Figma design BEFORE any component code is written or changed. Never
guess colors, spacing, or structure from a screenshot alone - always pull the real node data through the
Figma MCP server, then translate it into the codebase's existing PrimeVue + Tailwind conventions.

Output of this skill is a **discovery report**, not code. Hand it to `/primevue-component-build`.

## Required setup

- `.vscode/mcp.json` must contain the `figma-mcp` server entry:
  ```json
  "figma-mcp": { "type": "http", "url": "https://mcp.figma.com/mcp" }
  ```
  If it is missing or malformed, stop and say this is Frontend-Tooling-Specialist's responsibility - do not add it from this skill.
- The remote server requires the user to be signed into Figma and to approve the OAuth prompt in the IDE. If tool calls return an auth/permission error, ask the user to re-authenticate; never fall back to inventing values.
- Figma MCP tools used: `get_metadata`, `get_screenshot`, `get_design_context`, `get_variable_defs`, plus whatever design-system search the server exposes. Tool names are prefixed per workspace (here: `mcp_figma_mcp_ser_*`) - list the available tools rather than assuming an exact name.

## Vault touchpoints

- Run `/vault-search` for the component/page name and for prior Figma token mappings before starting, so you reuse decisions already made.
- After the implementation lands, the calling agent records the node -> component mapping and any NEW token mapping through `/vault-write`. If this skill discovers a token mapping not in the table below, say so explicitly in the report so it gets persisted.

## Procedure

1. **Parse the reference.** From `figma.com/design/:fileKey/:fileName?node-id=:nodeId`, extract `fileKey` and `nodeId`, converting `-` to `:` in the node id (`39-21141` -> `39:21141`). If only a node id was given with no file link, ask for the URL - never reuse a `fileKey` from a previous, unrelated task.
2. **Confirm the target.** Call `get_metadata` and/or `get_screenshot` for the node first, to check it is the right frame/component before pulling the full context (cheaper, catches a wrong node id early).
3. **Pull the real spec.** Call `get_design_context` for the node. Treat its output as a REFERENCE to adapt, not as final code - it knows nothing about this codebase's components, composables or token conventions.
4. **Resolve ambiguous raw values.** If a color/spacing value is not obviously a token, call `get_variable_defs` to resolve it to a named Figma variable, then map it with the table below.
5. **Check for an existing match first.** Search `ClientApp/src/components/common/` for an analogous piece (a Dialog shell, a button style, a paginator, a form field, a table). If the app already has a pattern for this UI, prefer reusing/extending the existing convention over the raw Figma pixel value. Introduce a new value only where there is a deliberate visual difference.
6. **Identify assets.** Note any image/icon that must be exported. Icons: prefer an equivalent PrimeIcon (`pi pi-*`). Real images: export and place under `Src/BookStore.Web/wwwroot/images/<page>/`, referenced by absolute URL (`/images/home/books.jpg`) - not imported through Vite.
7. **Map interaction and data.** Say which parts are static design and which are data-bound (rows, empty state, loading, error, pagination) - the data always comes from the `/api/*` endpoints via the composables, never from Figma sample content.
8. **Report the discovery** (hand-off to `/primevue-component-build`): node id + name, mapped color tokens, spacing/sizing (flagging which need Tailwind arbitrary values vs. the default scale), typography, icons/assets, which existing component(s) to mirror or extend, and which PrimeVue component + `pt` sections will carry the styling.

## Token mapping table (extend as new values appear - never leave a raw hex/px in code when a row below applies)

| Figma value observed | Use this Tailwind/PrimeVue token |
|---|---|
| `#334155` (dark slate) | `surface-700` (`bg-surface-700`, `border-surface-700`, `text-surface-700`) |
| `#1e293b` (darker slate, hover) | `surface-800` |
| `#cbd5e1` / light gray borders | `border-surface-300` |
| `#f8fafc` / very light row hover | `surface-50` / `surface-100` |
| `#ffffff` panel background | `bg-surface-0` |
| `#64748b` muted gray text | `text-muted-color` |
| primary body text (near-black slate) | `text-color` |
| Brand/primary action fill | `bg-primary` + `text-primary-contrast` (or `primaryButtonClass` from `styles/buttonStyles.ts`) |
| Icon glyphs | PrimeIcons `pi pi-*` - never inline SVG or Bootstrap Icons (`bi-*`) when an equivalent `pi-*` exists |

Recurring component-level mappings already derived from Figma (reuse, do not re-derive):

- Modal shell (rounded 21px white panel, 67px header, 3-gap footer) -> `dialogShellPt(width)` / `detailsDialogPt(width)` in `components/common/dialog/dialogStyles.ts`.
- Primary/secondary/danger action buttons -> `primaryButtonClass` / `secondaryButtonClass` / `dangerButtonClass` in `styles/buttonStyles.ts`.
- Row-action popup menu and the flat borderless paginator -> the `menuPt` / `paginatorPt` objects in `components/common/table/DataTableCommon.vue`.

## Rules

- Never fabricate Figma content - always call the MCP tools; if a call fails, say so rather than guessing values.
- Never hardcode a raw hex when a `surface-*`/`*-color` token is an exact or near match.
- Arbitrary Tailwind values (`rounded-[21px]`, `gap-1.75`, `w-[765px]`) are acceptable and already used throughout this codebase when the Figma value does not land on Tailwind's default scale - do not force a bad approximation to avoid one.
- Never copy Figma's generated code verbatim into a component - it ignores the existing composables, `pt` conventions and token utilities.
- Delete any temporary screenshot/reference asset after the implementation is verified against it.
- Always re-derive `fileKey`/`nodeId` from what the user provided in THIS task.
- In a new project, replace the token table above with that project's design-system mapping before using this skill in anger, and record it in that project's vault.

O servidor do Figma é declarado em mcp.json, junto do servidor do PrimeVue:

Autenticação do MCP do lado do Figma (Perfil/Settings/Security):


Esse arquivo é só a porta de entrada. O primevue roda localmente via npx, já o figma-mcp é remoto, então o GitHub Copilot precisa que o usuário esteja autenticado no Figma dentro do próprio VS Code para conseguir chamar as ferramentas do servidor. Sem essa configuração, a skill de descoberta é instruída a parar e avisar que a configuração do MCP é responsabilidade do agente de tooling, em vez de tentar contornar o problema.

A skill figma-discovery segue um roteiro fixo: primeiro interpreta a URL do Figma, extraindo o identificador do arquivo e o id do nó de design, depois confirma que pegou o frame certo com uma chamada mais barata (metadados ou uma captura de tela), e só então pede o contexto completo de design. Um exemplo real desse fluxo no BookStore foi a modal de confirmação de exclusão de um livro, implementada a partir do nó 58:25679 do Figma. Depois de confirmar o frame certo, o próximo passo do roteiro busca o spec completo:

  1. Parse da URL do Figma (fileKey + nodeId, convertendo "-" em ":")
  2. get_metadata / get_screenshot para confirmar o frame certo
  3. get_design_context para extrair cor, espaçamento, tipografia
  4. get_variable_defs para resolver variáveis nomeadas do Figma
  5. Busca por um componente equivalente já existente em common/
  6. Identificação de ícones/imagens a exportar
  7. Mapeamento do que é estático (design) vs dinâmico (dados da API)

get_metadata()

  • Confirma rapidamente se o nó apontado é realmente o frame certo antes de gastar uma chamada mais cara.

get_design_context()

  • Retorna a especificação completa do nó: cores, espaçamentos, tipografia e estrutura, tratada como referência a adaptar, não como código final.

get_variable_defs()

  • Resolve um valor bruto de cor ou espaçamento para a variável nomeada correspondente no arquivo Figma, quando o valor não bate obviamente com um token já conhecido.

Imagem: Frame da modal de confirmação de exclusão de livro aberto no Figma, nó 58:25679, mostrando o painel arredondado com o ícone de aviso e os botões Cancel/Delete

1 Passo: Copy link to selection

2 Passo: Colar o link no GitHub Copilot e selecionar o agente especializado em Front-End chamado Frontend-Specialist

3 Passo: O Agent Frontedn-Specialist, chama as Skills (figma-discovery e primevue-component-build) e verifica se já existe o componente criado dentro da pasta /common se não ele cria do zero, para que não haja duplicidade.

O passo mais importante desse roteiro não é técnico, é de disciplina: antes de aceitar qualquer valor extraído do Figma como definitivo, a skill manda procurar primeiro por um componente equivalente já existente em components/common/. Isso evitou reinventar a modal do zero, porque o BookStore já tinha estabelecido, num componente de exclusão anterior, um formato de Dialog reutilizável. Esse padrão foi extraído para um arquivo só de estilos:

const FORM_CONTENT_CLASS = "flex flex-col gap-[21px] px-6 py-2";
const DETAILS_CONTENT_CLASS = "flex flex-col gap-1.75 px-[21px] py-[17.5px]";

export function dialogShellPt(width: string, contentClass: string = FORM_CONTENT_CLASS) {
  return {
    root: { class: `${width} max-w-[92vw] rounded-[21px] border border-surface-300 bg-surface-0 p-0 overflow-hidden` },
    header: { class: "h-[67px] items-center justify-between pt-5 pb-4 pl-6 pr-4" },
    content: { class: contentClass },
    footer: { class: "gap-3 justify-end pb-5 pt-4 px-6" },
  };
}

dialogShellPt()

  • Monta o objeto de passthrough (pt) do Dialog do PrimeVue a partir de dois valores que variam por tela, a largura do modal e a classe de conteúdo, mantendo fixos os números extraídos do Figma para o cabeçalho de 67px e o arredondamento de 21px.

Repare que nenhum desses números é arbitrário. rounded-[21px]h-[67px] e gap-[21px] vieram diretamente do get_design_context do nó 58:25679, e como o Tailwind CSS padrão não tem uma escala que bata exatamente com 21px ou 67px, a convenção do projeto é usar valores arbitrários entre colchetes em vez de forçar uma aproximação para o valor de escala mais próximo. Essa mesma função é reaproveitada pelos diálogos de criação e edição de Livro e Autor, cada um só passando sua própria largura.

Com o shell de estilo pronto, o componente da modal de exclusão ficou assim:

<script setup lang="ts">
import Dialog from "primevue/dialog";
import Button from "primevue/button";
import Message from "primevue/message";
import { dialogShellPt, dialogSecondaryButtonClass, dialogDangerButtonClass } from "./dialogStyles";

const props = defineProps<{
  visible: boolean;
  title: string;
  message: string;
  details: { label: string; value: string }[];
  loading?: boolean;
  error?: string | null;
}>();

const dialogPt = dialogShellPt("w-[765px]");
</script>

defineProps()

  • Declara o contrato do componente: título, mensagem de aviso, uma lista de detalhes label/valor do registro a ser excluído, e os estados de carregamento e erro vindos da chamada à API.

dialogShellPt("w-[765px]")

  • Aplica a largura de 765px definida no Figma para esse modal específico, reaproveitando o mesmo objeto de passthrough usado pelos outros diálogos.

O pt (passthrough) é o mecanismo do PrimeVue que permite injetar classes do Tailwind diretamente nas seções internas de um componente, como root, header, content e footer, em vez de depender de uma única classe genérica no elemento raiz. É esse recurso que torna possível reproduzir com fidelidade um espaçamento de 67px de altura de cabeçalho sem escrever CSS customizado por fora do componente.

Depois que o componente está implementado, a segunda skill entra em ação, a primevue-component-build, que cuida da parte de verificação: rodar npx vue-tsc --noEmit para checar os tipos, depois npm run build para gerar o bundle final, e só então comparar o resultado renderizado contra o frame do Figma no navegador.

npx vue-tsc --noEmit
npm run build

O primeiro comando roda o compilador do Vue em modo de checagem, sem gerar arquivos, pegando erros de tipo nas props e nos emits do componente. O segundo executa o build de produção do Vite, que regenera wwwroot/dist/main.js e wwwroot/dist/main.css, os únicos artefatos que o ASP.NET Core efetivamente serve.

Imagem: Navegador mostrando a modal de confirmação de exclusão de um livro já implementada, lado a lado com o frame original do Figma para comparação

Imagem do figma:

Imagem: http://localhost:5045/Books

O resultado prático desse processo é que a modal de exclusão do BookStore não é uma aproximação visual, ela reproduz o painel de 21px de arredondamento, o cabeçalho de 67px e o rodapé com os botões Cancel e Delete exatamente como especificado no nó 58:25679, porque cada valor foi extraído do Figma via MCP e não digitado de memória. O mesmo padrão de dialogShellPt depois foi reaproveitado para os modais de criação de Livro e Autor, que só trocam a largura do painel (765px para Livro, 480px para Autor), mostrando como uma descoberta de design bem documentada vira um investimento que se paga em componentes futuros.

Na Parte 3 dessa série, a ideia é sair do design e entrar na memória de longo prazo do próprio GitHub Copilot, mostrando como o projeto usa um vault de documentação em Markdown para que decisões como essa (o formato do dialogShellPt, os nós do Figma já mapeados) não se percam entre uma sessão de chat e outra.

O código completo deste projeto está disponível no meu repositório no GitHub.

Links e Docks:

What is the Model Context Protocol (MCP)? - Model Context Protocol
PrimeVue | Vue UI Component Library
The ultimate collection of design-agnostic, flexible and accessible Vue UI Components.
Figma: The collaborative canvas for design, code, and AI
Figma is the canvas where design, code, and AI come together. From first idea to shipped product — go from concept to production with your whole team, in one place.

Não esqueça de me seguir no LinkedIn para mais conteúdos.
Até a próxima!!!

Confira mais:

Fique por dentro das novidades

Assine nossa newsletter e receba as últimas atualizações e artigos diretamente em seu email.

Assinar gratuitamente