Figma MCP × Claude Code × GitHub Actions: A Practical Guide to Design Token CI/CD Automation
Three days have passed since the designer changed the button color. No Slack announcement, no ticket. None of the developers know. Meanwhile, the components in the staging environment have quietly drifted from Figma, and only at the QA stage does a comment appear: "Why is the color different?" This phenomenon has a name: Design-Code Drift. It's a pain that any team running a design system has experienced at least once.
In 2025, Figma launched an official MCP (Model Context Protocol) server and, through its partnership with Anthropic, began offering deep integration with Claude Code. This article covers the entire process of configuring the Figma MCP server in a team repository and connecting the Figma REST API with GitHub Actions to build a fully automated design token pipeline where changes to Figma Variables automatically result in code PRs.
To follow this guide, you will need: A Figma Pro plan or higher (Starter has a rate limit of 6 requests per month, making it impractical for real-world use), basic experience with GitHub Actions, and a TypeScript and Node.js runtime environment. Setup will take approximately 2–3 hours.
Core Concepts
Figma MCP Server: How Is It Different from Screenshots?
The Figma MCP server is a bridge that provides Figma design data (node trees, auto layout, design tokens, component properties, etc.) directly to AI coding tools as context. It can be used with any MCP-compatible editor, including Claude Code, Cursor, VS Code Copilot, and Windsurf.
What is MCP (Model Context Protocol)? An open standard proposed by Anthropic that enables AI agents to communicate with external tools and data sources in a standardized way.
With the traditional screenshot-based approach, the LLM looks at an image and estimates color values. With MCP, the LLM directly accesses Figma's internal node structure. In particular, the get_variable_defs tool call extracts all variables from a selected frame, and the get_code_connect_data tool retrieves the actual component import paths. As a result, instead of hardcoded values like #3B82F6, the actual token name --color-primary-500 is used directly in the code.
Overall Architecture: A 3-Layer Structure
The system built in this guide consists of three layers.
┌─────────────────────────────────────────────┐
│ 1. MCP Layer │
│ Claude Code ↔ Figma MCP Server │
│ (Real-time design context lookup for local │
│ development) │
├─────────────────────────────────────────────┤
│ 2. Token Sync Layer │
│ Figma Variables → Style Dictionary │
│ → Platform-specific token files │
│ (CSS / iOS / Android) │
├─────────────────────────────────────────────┤
│ 3. CI/CD Layer │
│ GitHub Actions → Token change detection │
│ → Auto PR creation → Review → Deploy │
└─────────────────────────────────────────────┘Layer 1 (MCP) is for local development only, and Layer 3 (CI/CD) calls the Figma REST API directly. The reason MCP cannot be used directly in a CI/CD environment is that MCP is structured around a human authenticating through an editor. The automation pipeline uses a separate path that calls the REST API with a service token.
Style Dictionary v4 and the W3C Standard
What is a Design Token? A platform-independent representation of design decisions — such as colors, spacing, and typography — as variables. Like
--color-primary-500: #3B82F6, they are named values that serve as the core mechanism for maintaining design consistency across web, iOS, and Android.
Style Dictionary is a token transformation tool open-sourced by Amazon. It generates code for various platforms — CSS Variables, Swift, Kotlin, and more — from a single JSON source. Starting in 2025, the W3C Design Token Community Group's standard format has been integrated into Style Dictionary v4, significantly improving compatibility with Figma Variables.
// tokens/color.json (W3C standard format — based on Style Dictionary v4)
{
"color": {
"primary": {
"500": {
"$value": "#3B82F6",
"$type": "color",
"$description": "Primary brand color"
}
}
}
}Note on migrating from Style Dictionary v3 → v4: v4 introduces the W3C standard format (
$value,$type) along with breaking changes to the configuration file structure. Mixing an existing v3 project with the configuration in this guide may cause errors. If starting a new project, begin with v4; for existing projects, check the official migration guide first.
Practical Implementation
The three steps below are designed to be followed in order. Complete Step 1 (MCP setup) first, then Step 2 (token extraction script), and finally Step 3 (GitHub Actions integration).
Team-Shared MCP Configuration: Committing .mcp.json to Git
To share the same MCP server configuration across the entire team, commit .mcp.json to the project root. This way, when a new team member joins, they only need to set a single environment variable to immediately work in the same environment.
// .mcp.json (committed to project root)
{
"mcpServers": {
"figma": {
"type": "http",
"url": "https://mcp.figma.com/mcp",
"headers": {
"Authorization": "Bearer ${FIGMA_PERSONAL_ACCESS_TOKEN}"
}
}
}
}${FIGMA_PERSONAL_ACCESS_TOKEN} is an environment variable reference. Writing the actual token value directly in this file would expose the secret to Git, so you must always use the environment variable format.
Each team member adds their personal token to their local shell configuration (~/.zshrc or ~/.bashrc).
# ~/.zshrc
export FIGMA_PERSONAL_ACCESS_TOKEN="figd_your_personal_access_token"You can also register it directly from the Claude Code CLI.
# Register MCP at project scope
# --scope project: applies only to the current project
# --transport http: HTTP-based remote server method
claude mcp add --scope project --transport http figma https://mcp.figma.com/mcp
# Register at user global scope
# --scope user: applies to all projects for this user
claude mcp add --scope user --transport http figma https://mcp.figma.com/mcp| Configuration Method | Scope | Recommended Use |
|---|---|---|
Commit .mcp.json |
Entire project team | Shared team standard configuration |
--scope project |
Current project | Individual per-project additions |
--scope user |
User's entire environment | Individual global configuration |
Converting Figma Designs Directly into Components: How MCP Delivers Token Names
When you pass a Figma link to Claude Code with the MCP server connected, the following happens internally:
- Claude Code calls the
get_variable_defstool to extract all Variables from the target frame. - The
get_code_connect_datatool fetches the import path and Props interface of the connected code component. - Code generation begins with this information included in the LLM context.
# Run in a Claude Code session
claude "Implement the Button component from Figma file https://figma.com/file/ABC123
as a React component using our design system tokens"Because the LLM received actual token names like --color-primary-500, --spacing-2, and --border-radius-md as context via MCP, the resulting code uses token variable names directly without any hardcoding.
// Button component generated with MCP context
// Token names like --color-primary-500, --spacing-* came from the get_variable_defs response
import styles from './Button.module.css';
interface ButtonProps {
variant?: 'primary' | 'secondary';
size?: 'sm' | 'md' | 'lg';
children: React.ReactNode;
}
export function Button({ variant = 'primary', size = 'md', children }: ButtonProps) {
return (
<button className={`${styles.button} ${styles[variant]} ${styles[size]}`}>
{children}
</button>
);
}/* Button.module.css — token variable names used directly, no hardcoding */
.button {
background-color: var(--color-primary-500);
color: var(--color-neutral-0);
padding: var(--spacing-2) var(--spacing-4);
border-radius: var(--border-radius-md);
font-size: var(--font-size-sm);
}What is Code Connect? A Figma feature that links Figma components to actual code components, enabling MCP to deliver a component's real import path and Props interface to the LLM. Available on Organization/Enterprise plans. For smaller teams, a token sync pipeline can still be built using only Variables extraction, without Code Connect.
Building a Design Token CI/CD Pipeline with GitHub Actions
The complete automation flow is as follows:
Figma Variables change
↓
Figma REST API (/v1/files/:file_id/variables/local)
↓
tokens/figma-variables.json → Git Push
↓
GitHub Actions triggered
↓
Style Dictionary transformation (CSS / iOS / Android)
↓
Auto PR creation → Review → Merge → DeployStart with the token extraction script. The Figma REST API has two Variables endpoints. /v1/files/:file_id/variables returns all variables (including published library variables), while /v1/files/:file_id/variables/local returns only the local variables defined in that file. For the purpose of extracting design system tokens, use the /local endpoint.
// scripts/export-tokens.ts
import fs from 'fs/promises';
interface FigmaVariable {
id: string;
name: string;
resolvedType: string;
valuesByMode: Record<string, unknown>;
}
interface FigmaVariableCollection {
id: string;
name: string;
modes: Array<{ modeId: string; name: string }>;
defaultModeId: string;
}
function transformVariablesToTokens(
variables: Record<string, FigmaVariable>,
collections: Record<string, FigmaVariableCollection>
): Record<string, unknown> {
const tokens: Record<string, unknown> = {};
for (const variable of Object.values(variables)) {
const collection = collections[
Object.keys(collections).find(id =>
collections[id].name === variable.name.split('/')[0]
) ?? ''
];
const defaultModeId = collection?.defaultModeId;
const value = defaultModeId ? variable.valuesByMode[defaultModeId] : undefined;
// "color/primary/500" → { color: { primary: { "500": { $value, $type } } } }
const parts = variable.name.split('/');
let cursor = tokens;
for (let i = 0; i < parts.length - 1; i++) {
if (!cursor[parts[i]]) cursor[parts[i]] = {};
cursor = cursor[parts[i]] as Record<string, unknown>;
}
cursor[parts[parts.length - 1]] = {
$value: value,
$type: variable.resolvedType.toLowerCase(),
};
}
return tokens;
}
async function main() {
const fileId = process.env.FIGMA_FILE_ID;
const token = process.env.FIGMA_TOKEN;
if (!fileId || !token) {
throw new Error('FIGMA_FILE_ID and FIGMA_TOKEN environment variables are required');
}
// /local endpoint: returns only local variables defined in this file
const response = await fetch(
`https://api.figma.com/v1/files/${fileId}/variables/local`,
{ headers: { 'X-Figma-Token': token } }
);
if (!response.ok) {
throw new Error(`Figma API error: ${response.status} ${response.statusText}`);
}
const { meta } = await response.json();
const tokens = transformVariablesToTokens(
meta.variables,
meta.variableCollections
);
await fs.mkdir('tokens', { recursive: true });
await fs.writeFile('tokens/figma-variables.json', JSON.stringify(tokens, null, 2));
// Set GitHub Actions output
const changedCount = Object.keys(meta.variables).length;
const outputFile = process.env.GITHUB_OUTPUT;
if (outputFile) {
await fs.appendFile(outputFile, `changed_count=${changedCount}\n`);
}
console.log(`Token extraction complete: ${changedCount} tokens`);
}
main().catch(err => {
console.error(err);
process.exit(1);
});Here is the Style Dictionary configuration file. The example below uses ES Module (export default). For CommonJS environments, replace export default with module.exports =.
// style-dictionary.config.js (ES Module)
// For CommonJS: module.exports = { ... }
export default {
source: ['tokens/**/*.json'],
platforms: {
css: {
transformGroup: 'css',
buildPath: 'src/styles/tokens/',
files: [{ destination: 'variables.css', format: 'css/variables' }],
},
ios: {
transformGroup: 'ios-swift',
buildPath: 'ios/DesignTokens/',
files: [{ destination: 'DesignTokens.swift', format: 'ios-swift/class.swift' }],
},
android: {
transformGroup: 'android',
buildPath: 'android/src/main/res/values/',
files: [{ destination: 'design_tokens.xml', format: 'android/resources' }],
},
},
};Here is the complete GitHub Actions workflow.
# .github/workflows/sync-design-tokens.yml
name: Sync Design Tokens
on:
push:
paths:
- 'tokens/**'
workflow_dispatch:
inputs:
figma_file_id:
description: 'Figma File ID'
required: true
jobs:
sync-tokens:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
with:
version: 9
- name: Install dependencies
run: pnpm install
- name: Export Figma Variables
id: export
env:
FIGMA_TOKEN: ${{ secrets.FIGMA_PERSONAL_ACCESS_TOKEN }}
FIGMA_FILE_ID: ${{ github.event.inputs.figma_file_id || vars.FIGMA_FILE_ID }}
run: pnpm tokens:export
- name: Build Tokens
run: pnpm tokens:build
- name: Create Pull Request
uses: peter-evans/create-pull-request@v6
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: 'chore: sync design tokens from Figma'
title: '[Design System] Figma Token Auto-Sync'
branch: 'chore/sync-design-tokens'
body: |
Token changes automatically extracted from Figma Variables.
- Changed tokens: ${{ steps.export.outputs.changed_count }}
Please review before merging.steps.export.outputs.changed_count is automatically connected to the value written to GITHUB_OUTPUT in the export-tokens.ts script above.
Pros and Cons Analysis
Advantages
| Item | Details |
|---|---|
| Official Support | Stable updates guaranteed through the official Figma + Anthropic partnership |
| Direct Internal Data Access | Direct access to node structure yields higher accuracy than screenshot-based approaches |
| Code Connect Integration | Delivers actual component import paths and Props interfaces to the LLM |
| Simplified Setup | Remote server approach requires no desktop app installation; OAuth authentication can be completed within 5 minutes |
| Version Control Integration | Commit .mcp.json to Git so the entire team shares the same configuration |
Disadvantages and Caveats
| Item | Details | Mitigation |
|---|---|---|
| Rate Limits | Starter: 6/month, Pro: daily limit, Enterprise: 600/day | Consider a Pro or higher plan for team use |
| Cannot Run Directly in CI/CD | MCP authentication is not possible in serverless environments like GitHub Actions | Use the Figma REST API separately for token extraction |
| Beta Stability | Intermittent connection errors have been reported as it is still in beta | Always review generated code before use |
| Initial Adoption Cost | Full Code Connect setup requires 40–80 hours | Adopt incrementally, starting with token sync |
| Enterprise Requirements | Key features like Code Connect require an Organization/Enterprise plan | Smaller teams can substitute with direct Figma REST API calls |
The Most Common Mistakes in Practice
-
Hardcoding the token in
.mcp.json: Writing the actual value ofFIGMA_PERSONAL_ACCESS_TOKENdirectly in the file exposes the secret to Git. Always use the environment variable reference format (${FIGMA_PERSONAL_ACCESS_TOKEN}), and add.envfiles to.gitignore. -
Starting without a SSOT (Single Source of Truth) strategy: Starting without team agreement on whether Figma Variables or
tokens.jsonin Git is the source of truth will lead to conflicts between the two. Make this decision before building the pipeline. -
Defining token naming conventions after the fact: The Figma Variable name becomes the code token name (
--color-primary-500). Failing to establish a naming convention likecolor/primary/500at the point of initial adoption may require migrating all token names later.
Troubleshooting
Common issues encountered when setting up the pipeline.
If MCP connection fails: Check the registration status in Claude Code with claude mcp list. Verify that the FIGMA_PERSONAL_ACCESS_TOKEN environment variable is loaded in the current shell session (echo $FIGMA_PERSONAL_ACCESS_TOKEN) and that the token has not expired in Figma.
If tokens:export fails: Check the Figma API response code first. 403 indicates a token permission issue (check file access permissions); 404 means the FIGMA_FILE_ID is incorrect. If you hit the rate limit, you will receive a 429 response with a Retry-After header.
If Style Dictionary transformation fails: The configuration format differs between v3 and v4. The $value / $type keys are v4-only; v3 uses value / type. Check the installed version in package.json first.
Closing Thoughts
By combining the Figma MCP server with GitHub Actions, you can build a fully automated design system pipeline where a designer's Variables change automatically results in a code PR.
The smallest first step you can take right now is to create a single .mcp.json file and open a PR to your team repository. There is no need to adopt the entire setup all at once.
-
Configure MCP connection: Create a
.mcp.jsonfile in the project root and register thehttps://mcp.figma.com/mcpendpoint. Once each team member sets theFIGMA_PERSONAL_ACCESS_TOKENenvironment variable locally, the shared team MCP environment is ready. -
Write the token extraction script: Write
scripts/export-tokens.tsto call the Figma REST API at/v1/files/:file_id/variables/localand add the Style Dictionary v4 configuration file. Verify that it works locally first withpnpm tokens:export && pnpm tokens:build. -
Connect the GitHub Actions pipeline: Add the workflow to
.github/workflows/sync-design-tokens.yml, registerFIGMA_PERSONAL_ACCESS_TOKENin the repository Secrets andFIGMA_FILE_IDin Variables, and the pipeline that automatically creates a PR whenever thetokens/folder changes is complete.
Next article: We plan to cover a cross-platform design token strategy for simultaneously deploying to web, iOS, and Android using the W3C standard format in Style Dictionary v4.
References
Official Documentation
- Guide to the Figma MCP server | Figma Help
- Remote Server Installation | Figma Developer Docs
- Plans, access, and permissions | Figma Developer Docs
- Connect Claude Code to tools via MCP | Claude Code Docs
Blogs & Case Studies
- Claude Code + Figma MCP Server | Builder.io
- Introducing Claude Code to Figma | Figma Blog
- Design Systems And AI: Why MCP Servers Are The Unlock | Figma Blog
- Building a Figma-Driven MCP Production Pipeline | Francesca Tabor
- Syncing Figma Variables and Style Dictionary with GitHub Actions | James Ives
Tool Repositories