| name | figma-console-mcp-skills |
| description | Use powerful Figma Console MCP capabilities as Markdown skills for design tokens, variables, components, WCAG lint, a11y audits, version history, FigJam & Slides |
| triggers | ["export figma design tokens to code","audit figma file accessibility","generate component documentation from figma","import tokens into figma variables","analyze figma component variants","create figma version changelog","lint figma design for wcag compliance","manage figma variables programmatically"] |
Figma Console MCP Skills
Skill by ara.so — Design Skills collection.
A comprehensive collection of 22 self-contained skills that extend the native Figma MCP server with design-systems workflows: design token export/import (DTCG, CSS, Tailwind), variable management, component analysis, WCAG linting, accessibility audits, version history, and FigJam/Slides authoring. Each skill is a Markdown playbook with ready-to-paste JavaScript for the use_figma tool.
Installation
Prerequisites:
- Native Figma MCP server configured with OAuth
- The official
figma-use skill (provides Figma Plugin API reference)
- For 4 REST skills only: Figma personal access token in
$FIGMA_TOKEN
Option 1: Clone all skills
git clone https://github.com/PercentProduction/figma-console-mcp-skills-347.git
cd figma-console-mcp-skills-347
npm install
npm start
Option 2: Copy individual skills
cp -R figma-console-mcp-skills-347/figma-export-tokens ~/.claude/skills/
cp -R figma-console-mcp-skills-347/figma-lint-design ~/.claude/skills/
Option 3: Claude Desktop/Web (no terminal)
- Compress individual skill folders (e.g.,
figma-export-tokens.zip)
- Upload via Create / upload a skill in Claude
- Toggle skill on
Core Concepts
Skill Structure
Each skill is a folder containing:
SKILL.md — playbook with YAML frontmatter
scripts/*.js — ready-to-paste snippets for use_figma
scripts/*.mjs / *.sh — Node/bash for REST skills
references/ — skill-specific documentation (self-contained)
use_figma Conventions
Scripts follow these patterns:
const nodes = await figma.currentPage.findAll(n => n.type === 'FRAME');
const collectionName = "Brand Tokens";
return {
nodeCount: nodes.length,
data: nodes.map(n => ({ id: n.id, name: n.name }))
};
Key Skills Overview
🎨 Tokens & Variables
figma-export-tokens
Export Figma variables to code-ready formats (DTCG, CSS, Tailwind, SCSS, TypeScript).
Use case: Export design tokens for consumption in code
Example: Export to DTCG JSON
const collections = await figma.variables.getLocalVariableCollectionsAsync();
const collection = collections.find(c => c.name === "Semantic Tokens");
const modes = collection.modes;
const variables = await Promise.all(
collection.variableIds.map(id => figma.variables.getVariableByIdAsync(id))
);
async function resolveValue(value) {
if (value.type === 'VARIABLE_ALIAS') {
const aliasVar = await figma.variables.getVariableByIdAsync(value.id);
return resolveValue(aliasVar.valuesByMode[Object.keys(aliasVar.valuesByMode)[0]]);
}
return value;
}
const dtcg = {};
for (const variable of variables) {
for (const mode of modes) {
const rawValue = variable.valuesByMode[mode.modeId];
const resolved = await resolveValue(rawValue);
dtcg[variable.name] = {
$type: variable.resolvedType.toLowerCase(),
$value: resolved,
$description: variable.description || undefined
};
}
}
return JSON.stringify(dtcg, null, 2);
Example: Export to CSS Custom Properties
const collections = await figma.variables.getLocalVariableCollectionsAsync();
const vars = [];
for (const collection of collections) {
const variables = await Promise.all(
collection.variableIds.map(id => figma.variables.getVariableByIdAsync(id))
);
for (const variable of variables) {
const value = variable.valuesByMode[Object.keys(variable.valuesByMode)[0]];
const cssName = `--${variable.name.toLowerCase().replace(/\s+/g, '-')}`;
if (typeof value === 'object' && value.r !== undefined) {
const r = Math.round(value.r * 255);
const g = Math.round(value.g * 255);
const b = Math.round(value.b * 255);
vars.push(`${cssName}: rgb(${r}, ${g}, ${b});`);
} else if (typeof value === 'number') {
vars.push(`${cssName}: ${value}px;`);
} else {
vars.push(`${cssName}: ${value};`);
}
}
}
return `:root {\n ${vars.join('\n ')}\n}`;
figma-import-tokens
Push tokens (DTCG/JSON) into Figma as variables.
Use case: Sync tokens from code back into Figma
Example: Import DTCG JSON
const tokens = JSON.parse(dtcgJson);
let collection = await figma.variables.getLocalVariableCollectionsAsync()
.then(cols => cols.find(c => c.name === "Imported Tokens"));
if (!collection) {
collection = figma.variables.createVariableCollection("Imported Tokens");
}
for (const [name, token] of Object.entries(tokens)) {
const existing = await figma.variables.getLocalVariablesAsync()
.then(vars => vars.find(v => v.name === name));
let variable;
if (existing) {
variable = existing;
} else {
const resolvedType = token.$type === 'color' ? 'COLOR' : 'FLOAT';
variable = figma.variables.createVariable(name, collection.id, resolvedType);
}
const modeId = collection.modes[0].modeId;
if (token.$type === 'color') {
const hex = token.$value;
const r = parseInt(hex.slice(1, 3), 16) / 255;
const g = parseInt(hex.slice(3, 5), 16) / 255;
const b = parseInt(hex.slice(5, 7), 16) / 255;
variable.setValueForMode(modeId, { r, g, b });
} else {
variable.setValueForMode(modeId, parseFloat(token.$value));
}
if (token.$description) {
variable.description = token.$description;
}
}
return { imported: Object.keys(tokens).length };
figma-manage-variables
CRUD operations for variables: create, update, delete, batch operations, scopes.
Example: Batch create variables
const specs = [
{ name: "spacing/sm", value: 8, type: "FLOAT" },
{ name: "spacing/md", value: 16, type: "FLOAT" },
{ name: "spacing/lg", value: 24, type: "FLOAT" }
];
const collection = figma.variables.createVariableCollection("Spacing");
const modeId = collection.modes[0].modeId;
for (const spec of specs) {
const variable = figma.variables.createVariable(spec.name, collection.id, spec.type);
variable.setValueForMode(modeId, spec.value);
variable.scopes = ['ALL_SCOPES'];
}
return { created: specs.length, collectionId: collection.id };
🧩 Components & Design System
figma-analyze-component-set
Extract variant state machine, CSS pseudo-class mappings, visual diffs.
Example: Extract variant states
const componentSet = figma.currentPage.selection[0];
if (componentSet.type !== 'COMPONENT_SET') {
throw new Error('Select a component set');
}
const variants = componentSet.children.filter(c => c.type === 'COMPONENT');
const properties = {};
for (const variant of variants) {
const props = variant.name.split(', ').reduce((acc, pair) => {
const [key, value] = pair.split('=');
acc[key.trim()] = value.trim();
return acc;
}, {});
for (const [key, value] of Object.entries(props)) {
if (!properties[key]) properties[key] = new Set();
properties[key].add(value);
}
}
const stateMachine = Object.fromEntries(
Object.entries(properties).map(([k, v]) => [k, Array.from(v)])
);
const cssPseudoMap = {};
if (stateMachine.State) {
cssPseudoMap.State = {
'Hover': ':hover',
'Active': ':active',
'Focus': ':focus',
'Disabled': ':disabled'
};
}
return { stateMachine, cssPseudoMap, variantCount: variants.length };
figma-deep-component
Unlimited-depth component tree with resolved tokens and mainComponent refs.
Example: Deep traverse component
async function deepTraverse(node, depth = 0) {
const result = {
id: node.id,
name: node.name,
type: node.type,
depth
};
if (node.type === 'INSTANCE') {
result.mainComponent = {
id: node.mainComponent.id,
name: node.mainComponent.name,
key: node.mainComponent.key
};
}
if (node.boundVariables) {
result.boundVariables = {};
for (const [field, binding] of Object.entries(node.boundVariables)) {
if (binding.type === 'VARIABLE_ALIAS') {
const variable = await figma.variables.getVariableByIdAsync(binding.id);
result.boundVariables[field] = {
name: variable.name,
value: variable.valuesByMode[Object.keys(variable.valuesByMode)[0]]
};
}
}
}
if ('children' in node) {
result.children = await Promise.all(
node.children.map(child => deepTraverse(child, depth + 1))
);
}
return result;
}
const selected = figma.currentPage.selection[0];
return await deepTraverse(selected);
♿ Quality & Accessibility
figma-lint-design
WCAG 2.2 + design-system quality lint over node tree.
Example: Color contrast check
function getContrast(fg, bg) {
const luminance = (c) => {
const val = c / 255;
return val <= 0.03928 ? val / 12.92 : Math.pow((val + 0.055) / 1.055, 2.4);
};
const l1 = 0.2126 * luminance(fg.r * 255) + 0.7152 * luminance(fg.g * 255) + 0.0722 * luminance(fg.b * 255);
const l2 = 0.2126 * luminance(bg.r * 255) + 0.7152 * luminance(bg.g * 255) + 0.0722 * luminance(bg.b * 255);
return (Math.max(l1, l2) + 0.05) / (Math.min(l1, l2) + 0.05);
}
const issues = [];
const textNodes = figma.currentPage.findAll(n => n.type === 'TEXT');
for (const node of textNodes) {
const fontSize = node.fontSize;
const fills = node.fills;
if (fills.length > 0 && fills[0].type === 'SOLID') {
const parent = node.parent;
let bgColor = { r: 1, g: 1, b: 1 };
if (parent.fills && parent.fills.length > 0 && parent.fills[0].type === 'SOLID') {
bgColor = parent.fills[0].color;
}
const contrast = getContrast(fills[0].color, bgColor);
const minContrast = fontSize >= 18 ? 3 : 4.5;
if (contrast < minContrast) {
issues.push({
nodeId: node.id,
nodeName: node.name,
issue: `Insufficient contrast: ${contrast.toFixed(2)}:1 (needs ${minContrast}:1)`,
wcag: 'WCAG 2.2 Level AA - 1.4.3 Contrast (Minimum)'
});
}
}
}
return { totalIssues: issues.length, issues };
figma-audit-accessibility
Per-component a11y scorecard: state coverage, focus, target size.
Example: Check touch target size
const MIN_TOUCH_TARGET = 44;
const interactiveNodes = figma.currentPage.findAll(n =>
n.type === 'INSTANCE' &&
(n.name.includes('Button') || n.name.includes('Input'))
);
const violations = [];
for (const node of interactiveNodes) {
if (node.width < MIN_TOUCH_TARGET || node.height < MIN_TOUCH_TARGET) {
violations.push({
nodeId: node.id,
nodeName: node.name,
size: { width: node.width, height: node.height },
issue: `Touch target too small (minimum ${MIN_TOUCH_TARGET}x${MIN_TOUCH_TARGET}px)`,
wcag: 'WCAG 2.2 Level AAA - 2.5.5 Target Size'
});
}
}
return {
totalChecked: interactiveNodes.length,
violations: violations.length,
details: violations
};
🕓 Versioning & Collaboration (REST API)
Setup required:
export FIGMA_TOKEN="figd_your_token_here"
figma-version-history
List versions, snapshot a version, diff two versions.
Example: List recent versions (Node.js)
const fileKey = process.argv[2];
const token = process.env.FIGMA_TOKEN;
const response = await fetch(
`https://api.figma.com/v1/files/${fileKey}/versions`,
{ headers: { 'X-Figma-Token': token } }
);
const data = await response.json();
console.log(JSON.stringify(data.versions.slice(0, 10), null, 2));
figma-generate-changelog
Human-readable markdown changelog between versions.
Example: Generate changelog
const fileKey = process.argv[2];
const fromVersion = process.argv[3];
const toVersion = process.argv[4];
const token = process.env.FIGMA_TOKEN;
const [v1, v2] = await Promise.all([
fetch(`https://api.figma.com/v1/files/${fileKey}?version=${fromVersion}`, {
headers: { 'X-Figma-Token': token }
}).then(r => r.json()),
fetch(`https://api.figma.com/v1/files/${fileKey}?version=${toVersion}`, {
headers: { 'X-Figma-Token': token }
}).then(r => r.json())
]);
const changes = [];
const v1Nodes = new Set(Object.keys(v1.document));
const v2Nodes = new Set(Object.keys(v2.document));
const added = [...v2Nodes].filter(id => !v1Nodes.has(id));
const removed = [...v1Nodes].filter(id => !v2Nodes.has(id));
console.log(`# Changelog\n\n## Added (${added.length})\n`);
console.log(`## Removed (${removed.length})\n`);
figma-comments
Read / post / reply / delete file comments.
Example: List comments
const fileKey = process.argv[2];
const token = process.env.FIGMA_TOKEN;
const response = await fetch(
`https://api.figma.com/v1/files/${fileKey}/comments`,
{ headers: { 'X-Figma-Token': token } }
);
const data = await response.json();
for (const comment of data.comments) {
console.log(`${comment.user.handle}: ${comment.message}`);
console.log(` Created: ${new Date(comment.created_at).toLocaleString()}`);
if (comment.client_meta?.node_id) {
console.log(` Node: ${comment.client_meta.node_id}`);
}
}
Example: Post comment
const fileKey = process.argv[2];
const message = process.argv[3];
const nodeId = process.argv[4];
const token = process.env.FIGMA_TOKEN;
const body = {
message,
...(nodeId && { client_meta: { node_id: [nodeId] } })
};
await fetch(`https://api.figma.com/v1/files/${fileKey}/comments`, {
method: 'POST',
headers: {
'X-Figma-Token': token,
'Content-Type': 'application/json'
},
body: JSON.stringify(body)
});
📝 Documentation & Annotations
figma-generate-component-doc
Generate complete component documentation markdown.
Example: Document component
const component = figma.currentPage.selection[0];
if (component.type !== 'COMPONENT') {
throw new Error('Select a component');
}
const doc = [`# ${component.name}\n`];
doc.push(`**Description:** ${component.description || 'No description'}\n`);
if (component.componentPropertyDefinitions) {
doc.push(`## Properties\n`);
for (const [name, def] of Object.entries(component.componentPropertyDefinitions)) {
doc.push(`- **${name}** (${def.type})`);
if (def.type === 'VARIANT' && def.variantOptions) {
doc.push(` - Options: ${def.variantOptions.join(', ')}`);
}
if (def.defaultValue !== undefined) {
doc.push(` - Default: ${def.defaultValue}`);
}
}
doc.push('');
}
const boundVars = [];
function findBoundVariables(node) {
if (node.boundVariables) {
for (const [field, binding] of Object.entries(node.boundVariables)) {
if (binding.type === 'VARIABLE_ALIAS') {
boundVars.push({ nodeId: node.id, field, varId: binding.id });
}
}
}
if ('children' in node) {
node.children.forEach(findBoundVariables);
}
}
findBoundVariables(component);
if (boundVars.length > 0) {
doc.push(`## Design Tokens (${boundVars.length})\n`);
for (const { field, varId } of boundVars) {
const variable = await figma.variables.getVariableByIdAsync(varId);
doc.push(`- **${field}**: \`${variable.name}\``);
}
}
return doc.join('\n');
🎯 FigJam & Slides
figjam-create-content
Author FigJam: stickies, connectors, shapes, tables.
Example: Create sticky note
const sticky = figma.createSticky();
sticky.x = 100;
sticky.y = 100;
sticky.resize(200, 200);
await figma.loadFontAsync({ family: "Inter", style: "Regular" });
sticky.text.characters = "TODO: Design tokens export";
sticky.fills = [{ type: 'SOLID', color: { r: 1, g: 0.9, b: 0.2 } }];
return { id: sticky.id, name: sticky.name };
Example: Create connector
const nodes = figma.currentPage.selection;
if (nodes.length < 2) throw new Error('Select 2+ nodes');
const connector = figma.createConnector();
connector.connectorStart = {
endpointNodeId: nodes[0].id,
magnet: 'AUTO'
};
connector.connectorEnd = {
endpointNodeId: nodes[1].id,
magnet: 'AUTO'
};
return { id: connector.id };
figma-slides
Author Figma Slides: create/reorder slides, text/shapes.
Example: Create slide with title
const slide = figma.createSlide();
slide.name = "Design System Overview";
await figma.loadFontAsync({ family: "Inter", style: "Bold" });
const title = figma.createText();
title.characters = "Design System Overview";
title.fontSize = 48;
title.x = 100;
title.y = 100;
slide.appendChild(title);
return { slideId: slide.id, titleId: title.id };
Common Patterns
Error Handling
try {
const result = await someOperation();
return { success: true, data: result };
} catch (error) {
return { success: false, error: error.message };
}
Loading Fonts
await figma.loadFontAsync({ family: "Inter", style: "Regular" });
const text = figma.createText();
text.characters = "Hello";
Async Variable Resolution
const collections = await figma.variables.getLocalVariableCollectionsAsync();
const variables = await Promise.all(
collections[0].variableIds.map(id => figma.variables.getVariableByIdAsync(id))
);
Finding Nodes
const textNodes = figma.currentPage.findAll(node => node.type === 'TEXT');
const button = figma.currentPage.findOne(
node => node.type === 'COMPONENT' && node.name.includes('Button')
);
Troubleshooting
"Script failed on first use_figma call"
- Scripts are atomic — nothing applied if it errors
- Check error message for specific API issue
- Verify you're in the correct file type (Design vs FigJam vs Slides)
"Cannot read property of undefined"
- Ensure node selection is correct type
- Use type guards:
if (node.type !== 'COMPONENT') throw new Error(...)
"Font not found"
- Always call
figma.loadFontAsync() before creating/editing text
- Check font name and style match exactly
"Variable not found"
- Variables require async:
await figma.variables.getVariableByIdAsync(id)
- Check variable exists in current file
"REST skills not working"
- Verify
$FIGMA_TOKEN environment variable is set
- Confirm token has required scopes (file:read, file:write)
- REST skills only work in terminal-capable agents
"Skill not loading"
- Ensure
SKILL.md is at root of skill folder
- For zips: compress the folder contents, not the folder itself
- Verify YAML frontmatter is valid
Configuration
Enable REST Skills:
export FIGMA_TOKEN="figd_your_token_here"
echo 'export FIGMA_TOKEN="figd_your_token_here"' >> ~/.zshrc
Figma MCP OAuth:
- Configured once when adding Figma connector
- No token management needed for
use_figma skills
- OAuth session automatically used by native tools
Best Practices
- Always load
figma-use skill first — provides Plugin API reference
- Batch operations — minimize async calls in loops
- Validate inputs — check node types before operations
- Self-contained skills — each skill folder has everything it needs
- Environment variables — never hardcode tokens/secrets
- Error messages — return structured error objects for debugging
- Type safety — use TypeScript hints in comments for clarity
Skill Invocation
Implicit: Natural language triggers skill matching
"Export my figma design tokens to CSS variables"
→ loads figma-export-tokens
Explicit: Direct skill invocation
/figma-lint-design
/figma-export-tokens
Further Resources