"Life is Full of Possibilities" - Soul, 2020

IT (프론트엔드)

MCP와 Cursor를 활용한 Figma 컴포넌트 퍼블리싱

m2ndy 2025. 4. 6. 21:20

 

 

MCP란?

 

MCP (Model Context Protocol)는 애플리케이션이 LLM(Large Language Model)에 컨텍스트를 제공하는 방식을 표준화한 오픈 프로토콜입니다.
기존에는 각 어플리케이션마다 LLM에 정보를 전달하는 방식이 달라 어플리케이션에 맞는 방식으로 개별 작업을 해야 했는데요, MCP는 클라이언트와 서버, 그리고 LLM 간의 인터페이스를 표준화하여 한 번의 설정만으로 여러 어플리케이션에 작업을 수행할 수 있도록 합니다.

 

공식 문서를 통해 MCP의 동작 구조와 개념을 자세히 이해할 수 있습니다.
modelcontextprotocol.io

 

 

MCP Architecture @MCP

 

 

MCP + Cursor 연동 방법

 

1. Figma MCP 서버 구성

Cursor에서 Figma 컴포넌트를 인식하려면 MCP 서버를 설정해야 합니다.
먼저 아래 GitHub 저장소를 참고해 MCP 설정 코드를 가져옵니다.

Figma-Context-MCP GitHub Repository

 

 

2. MCP 설정 추가 (Cursor Settings)

Cursor의 Settings > MCP 설정 메뉴에 아래 설정을 추가해주세요.

--figma-api-key에는 발급받은 Figma Personal Access Token을 입력합니다.
👉 Figma Token 발급 방법 보러 가기

 

 

Mac / Linux 환경

{
  "mcpServers": {
    "Framelink Figma MCP": {
      "command": "npx",
      "args": ["-y", "figma-developer-mcp", "--figma-api-key=YOUR-KEY", "--stdio"]
    }
  }
}

 

 

Windows 환경

{
  "mcpServers": {
    "Framelink Figma MCP": {
      "command": "cmd",
      "args": ["/c", "npx", "-y", "figma-developer-mcp", "--figma-api-key=YOUR-KEY", "--stdio"]
    }
  }
}

 

이 설정을 통해 Cursor에서 Figma 컴포넌트 정보를 직접 읽어올 수 있는 서버가 활성화됩니다.
Figma에 만들어진 UI를 코드로 변환할 수 있게 되는 것이죠.

 

 

 

 

프로젝트에 Figma MCP 적용해 보기

 

그럼 프로젝트에 적용해 볼까요?

 

새로운 프로젝트를 생성하여 "메리 트리스마스" 프로젝트에 디자인된 버튼을 코드를 작성하지 않고 구현해 보겠습니다. 

 

 

 

먼저 Figma에서 구현하려는 디자인의 링크를 복사합니다.

저는 공통으로 사용될 버튼을 구현할 것이기 때문에, Variants가 있는 Section을 복사하겠습니다.

 

 

 

복사한 링크를 Cursor Chat에 붙여 넣고, 추가로 실행할 내용을 작성합니다.

아래는 제가 작성한 예시입니다.

 

 

 

유의해야 할 점은 Cursor Chat 모드를 Agent로 지정해야 한다는 것입니다. 

Chat(일반) 모드는 단순히 질의응답을 할 때, 프롬프트 기반의 대화를 할 수 있지만

Agent 모드에서는 Cursor가 코드 베이스를 이해하고 분석한 뒤, 이를 기반으로 작업을 수행합니다. 폴더 구조나 기존 컨텍스트를 파악하기 때문에 복잡한 작업에 적합한 모드입니다.

 

 

 

 

 

 

MCP가 컴포넌트 디자인을 분석하여 구현한 Button 컴포넌트 결과물을 볼까요?

 

 

 

MCP로 구현한 결과물

 

 

피그마 디자인

 

피그마 디자인과 100% 일치하지는 않지만, 대략적으로 비슷한 느낌입니다.

 

코드를 살펴보자면, 폴더 구조까지 설계하여 파일을 생성하는 것을 확인할 수 있습니다. React 프로젝트에서 범용적으로 사용되는 폴더 구조를 만들어 주네요.

src/
├── types/
│   └── button.ts         // 버튼 관련 타입 정의
│
└── components/
    └── common/
        └── Button/
            ├── Button.tsx    // 버튼 컴포넌트 로직
            └── style.ts      // 버튼 스타일 정의

 

 

👇 구현된 코드

더보기
// components/common/Button/style.ts

import styled, { css } from 'styled-components'
import { ButtonProps, ButtonVariant, ButtonSize } from '../../../types/button'

const VARIANT_STYLES = {
  default: css`
    background-color: white;
    color: #C2282D;
    border: 1px solid #C2282D;
    
    &:hover:not(:disabled) {
      background-color: #f9fafb;
    }
  `,
  primary: css`
    background-color: #C2282C;
    color: white;
    
    &:hover:not(:disabled) {
      background-color: #A11F23;
    }
  `,
  secondary: css`
    background-color: #2E882B;
    color: white;
    
    &:hover:not(:disabled) {
      background-color: #246A22;
    }
  `
}

const SIZE_STYLES = {
  sm: css`
    padding: 0.5rem 0.75rem;
    font-size: 0.75rem;
    border-radius: 0.75rem;
  `,
  md: css`
    padding: 0.75rem 1rem;
    font-size: 0.75rem;
    border-radius: 0.9375rem;
    min-width: 321px;
    height: 44px;
  `,
  lg: css`
    padding: 1rem 1.5rem;
    font-size: 0.875rem;
    border-radius: 0.9375rem;
  `
}

export const StyledButton = styled.button<ButtonProps>`
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: 'Inter', sans-serif;
  font-weight: 700;
  transition: all 0.2s;
  cursor: pointer;
  
  ${({ variant = 'default' }) => VARIANT_STYLES[variant as ButtonVariant]}
  ${({ size = 'md' }) => SIZE_STYLES[size as ButtonSize]}
  ${({ fullWidth }) => fullWidth && css`width: 100%;`}
  
  &:disabled {
    opacity: 0.5;
    cursor: not-allowed;
  }
  
  ${({ isLoading }) => isLoading && css`
    opacity: 0.7;
    cursor: wait;
  `}
`

export const IconWrapper = styled.span<{ position: 'left' | 'right' }>`
  display: inline-flex;
  align-items: center;
  margin-${props => props.position === 'left' ? 'right' : 'left'}: 0.5rem;
`

export const LoadingSpinner = styled.svg`
  animation: spin 1s linear infinite;
  margin-right: 0.5rem;
  
  @keyframes spin {
    from {
      transform: rotate(0deg);
    }
    to {
      transform: rotate(360deg);
    }
  }
`

 

// components/common/Button/Button.tsx

import React from 'react'
import { ButtonProps } from '../../../types/button'
import { StyledButton, IconWrapper, LoadingSpinner } from './style'

const Button: React.FC<ButtonProps> = ({
  variant = 'default',
  size = 'md',
  fullWidth = false,
  isLoading = false,
  leftIcon,
  rightIcon,
  children,
  disabled,
  ...props
}) => {
  return (
    <StyledButton
      variant={variant}
      size={size}
      fullWidth={fullWidth}
      isLoading={isLoading}
      disabled={disabled || isLoading}
      {...props}
    >
      {isLoading ? (
        <LoadingSpinner
          width="16"
          height="16"
          viewBox="0 0 24 24"
          xmlns="http://www.w3.org/2000/svg"
          fill="none"
        >
          <circle
            className="opacity-25"
            cx="12"
            cy="12"
            r="10"
            stroke="currentColor"
            strokeWidth="4"
          />
          <path
            className="opacity-75"
            fill="currentColor"
            d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
          />
        </LoadingSpinner>
      ) : (
        leftIcon && <IconWrapper position="left">{leftIcon}</IconWrapper>
      )}
      {children}
      {rightIcon && <IconWrapper position="right">{rightIcon}</IconWrapper>}
    </StyledButton>
  )
}

export default Button

 

// constants/colors.ts

export const BUTTON_COLORS = {
  primary: {
    background: '#C2282C',
    text: '#FFFFFF',
    hover: '#A11F23',
    active: '#8A1A1E'
  },
  secondary: {
    background: '#2E882B',
    text: '#FFFFFF',
    hover: '#246A22',
    active: '#1B511A'
  },
  default: {
    background: '#FFFFFF',
    text: '#C2282D',
    border: '#C2282D',
    hover: '#F8F8F8',
    active: '#F0F0F0'
  }
} as const

export type ButtonVariant = keyof typeof BUTTON_COLORS

 

 

 

 

 

 

놀라웠던 점은, 제가 언급하지 않았던 Loading Spinner까지 추가해 주었다는 것입니다.

버튼 컴포넌트에서 자주 사용되는 요소들까지 임의로 판단하여 컴포넌트에 넣어주었습니다.

 

 

 

 

 

 

 

 

느낀 점

 

Figma에서 MCP가 연동되면서 퍼블리싱 속도가 빨라질 것으로 예상합니다. 확실히 명령어만으로 단 한 줄의 코드 작성 없이 컴포넌트를 뚝딱 만들어내니 편리하다는 생각이 들어요. 그렇지만 디자인을 100% 동일하게 구현하지는 못한다는 한계점이 있습니다. 디자인을 변경하기 위해 프롬프트를 작성하거나 코드를 수정하는 작업이 필요합니다.

 

하지만 Figma MCP가 아직 초기 단계인 것을 감안하면 나쁘지 않은 결과를 보여주고 있다고 생각합니다. Figma MCP와 Cursor가 발전함에 따라 컨텍스트를 더 정밀하게 반영할 수 있을 것 같습니다. 팀 컨벤션에 맞는 프롬프트를 작성하고, Cursor가 프로젝트의 맥락을 갖고 있는 상태에서는 좋은 결과물을 보여줄 것이라고 예상합니다. 앞으로 Figma MCP를 통해 프론트엔드 개발자들에게 퍼블리싱 작업의 시간을 단축시킬 수 있기를 기대해 봅니다.