Migrated from design-system-swarm with fresh git history.
Old project history preserved in /home/overbits/apps/design-system-swarm
Core components:
- MCP Server (Python FastAPI with mcp 1.23.1)
- Claude Plugin (agents, commands, skills, strategies, hooks, core)
- DSS Backend (dss-mvp1 - token translation, Figma sync)
- Admin UI (Node.js/React)
- Server (Node.js/Express)
- Storybook integration (dss-mvp1/.storybook)
Self-contained configuration:
- All paths relative or use DSS_BASE_PATH=/home/overbits/dss
- PYTHONPATH configured for dss-mvp1 and dss-claude-plugin
- .env file with all configuration
- Claude plugin uses ${CLAUDE_PLUGIN_ROOT} for portability
Migration completed: $(date)
🤖 Clean migration with full functionality preserved
261 lines
7.5 KiB
Python
261 lines
7.5 KiB
Python
"""
|
|
Figma Integration for MCP
|
|
|
|
Provides Figma API tools through circuit breaker pattern.
|
|
"""
|
|
|
|
import httpx
|
|
from typing import Dict, Any, List, Optional
|
|
from mcp import types
|
|
|
|
from .base import BaseIntegration
|
|
from ..config import integration_config
|
|
|
|
|
|
# Figma MCP Tool Definitions
|
|
FIGMA_TOOLS = [
|
|
types.Tool(
|
|
name="figma_get_file",
|
|
description="Get Figma file metadata and structure",
|
|
inputSchema={
|
|
"type": "object",
|
|
"properties": {
|
|
"file_key": {
|
|
"type": "string",
|
|
"description": "Figma file key"
|
|
}
|
|
},
|
|
"required": ["file_key"]
|
|
}
|
|
),
|
|
types.Tool(
|
|
name="figma_get_styles",
|
|
description="Get design styles (colors, text, effects) from Figma file",
|
|
inputSchema={
|
|
"type": "object",
|
|
"properties": {
|
|
"file_key": {
|
|
"type": "string",
|
|
"description": "Figma file key"
|
|
}
|
|
},
|
|
"required": ["file_key"]
|
|
}
|
|
),
|
|
types.Tool(
|
|
name="figma_get_components",
|
|
description="Get component definitions from Figma file",
|
|
inputSchema={
|
|
"type": "object",
|
|
"properties": {
|
|
"file_key": {
|
|
"type": "string",
|
|
"description": "Figma file key"
|
|
}
|
|
},
|
|
"required": ["file_key"]
|
|
}
|
|
),
|
|
types.Tool(
|
|
name="figma_extract_tokens",
|
|
description="Extract design tokens (variables) from Figma file",
|
|
inputSchema={
|
|
"type": "object",
|
|
"properties": {
|
|
"file_key": {
|
|
"type": "string",
|
|
"description": "Figma file key"
|
|
}
|
|
},
|
|
"required": ["file_key"]
|
|
}
|
|
),
|
|
types.Tool(
|
|
name="figma_get_node",
|
|
description="Get specific node/component by ID from Figma file",
|
|
inputSchema={
|
|
"type": "object",
|
|
"properties": {
|
|
"file_key": {
|
|
"type": "string",
|
|
"description": "Figma file key"
|
|
},
|
|
"node_id": {
|
|
"type": "string",
|
|
"description": "Node ID to fetch"
|
|
}
|
|
},
|
|
"required": ["file_key", "node_id"]
|
|
}
|
|
)
|
|
]
|
|
|
|
|
|
class FigmaIntegration(BaseIntegration):
|
|
"""Figma API integration with circuit breaker"""
|
|
|
|
FIGMA_API_BASE = "https://api.figma.com/v1"
|
|
|
|
def __init__(self, config: Dict[str, Any]):
|
|
"""
|
|
Initialize Figma integration.
|
|
|
|
Args:
|
|
config: Must contain 'api_token' or use FIGMA_TOKEN from env
|
|
"""
|
|
super().__init__("figma", config)
|
|
self.api_token = config.get("api_token") or integration_config.FIGMA_TOKEN
|
|
|
|
if not self.api_token:
|
|
raise ValueError("Figma API token not configured")
|
|
|
|
self.headers = {
|
|
"X-Figma-Token": self.api_token
|
|
}
|
|
|
|
async def get_file(self, file_key: str) -> Dict[str, Any]:
|
|
"""
|
|
Get Figma file metadata and structure.
|
|
|
|
Args:
|
|
file_key: Figma file key
|
|
|
|
Returns:
|
|
File data
|
|
"""
|
|
async def _fetch():
|
|
async with httpx.AsyncClient() as client:
|
|
response = await client.get(
|
|
f"{self.FIGMA_API_BASE}/files/{file_key}",
|
|
headers=self.headers,
|
|
timeout=30.0
|
|
)
|
|
response.raise_for_status()
|
|
return response.json()
|
|
|
|
return await self.call_api(_fetch)
|
|
|
|
async def get_styles(self, file_key: str) -> Dict[str, Any]:
|
|
"""
|
|
Get all styles from Figma file.
|
|
|
|
Args:
|
|
file_key: Figma file key
|
|
|
|
Returns:
|
|
Styles data
|
|
"""
|
|
async def _fetch():
|
|
async with httpx.AsyncClient() as client:
|
|
response = await client.get(
|
|
f"{self.FIGMA_API_BASE}/files/{file_key}/styles",
|
|
headers=self.headers,
|
|
timeout=30.0
|
|
)
|
|
response.raise_for_status()
|
|
return response.json()
|
|
|
|
return await self.call_api(_fetch)
|
|
|
|
async def get_components(self, file_key: str) -> Dict[str, Any]:
|
|
"""
|
|
Get all components from Figma file.
|
|
|
|
Args:
|
|
file_key: Figma file key
|
|
|
|
Returns:
|
|
Components data
|
|
"""
|
|
async def _fetch():
|
|
async with httpx.AsyncClient() as client:
|
|
response = await client.get(
|
|
f"{self.FIGMA_API_BASE}/files/{file_key}/components",
|
|
headers=self.headers,
|
|
timeout=30.0
|
|
)
|
|
response.raise_for_status()
|
|
return response.json()
|
|
|
|
return await self.call_api(_fetch)
|
|
|
|
async def extract_tokens(self, file_key: str) -> Dict[str, Any]:
|
|
"""
|
|
Extract design tokens (variables) from Figma file.
|
|
|
|
Args:
|
|
file_key: Figma file key
|
|
|
|
Returns:
|
|
Variables/tokens data
|
|
"""
|
|
async def _fetch():
|
|
async with httpx.AsyncClient() as client:
|
|
# Get local variables
|
|
response = await client.get(
|
|
f"{self.FIGMA_API_BASE}/files/{file_key}/variables/local",
|
|
headers=self.headers,
|
|
timeout=30.0
|
|
)
|
|
response.raise_for_status()
|
|
return response.json()
|
|
|
|
return await self.call_api(_fetch)
|
|
|
|
async def get_node(self, file_key: str, node_id: str) -> Dict[str, Any]:
|
|
"""
|
|
Get specific node from Figma file.
|
|
|
|
Args:
|
|
file_key: Figma file key
|
|
node_id: Node ID
|
|
|
|
Returns:
|
|
Node data
|
|
"""
|
|
async def _fetch():
|
|
async with httpx.AsyncClient() as client:
|
|
response = await client.get(
|
|
f"{self.FIGMA_API_BASE}/files/{file_key}/nodes",
|
|
headers=self.headers,
|
|
params={"ids": node_id},
|
|
timeout=30.0
|
|
)
|
|
response.raise_for_status()
|
|
return response.json()
|
|
|
|
return await self.call_api(_fetch)
|
|
|
|
|
|
class FigmaTools:
|
|
"""MCP tool executor for Figma integration"""
|
|
|
|
def __init__(self, config: Dict[str, Any]):
|
|
"""
|
|
Args:
|
|
config: Figma configuration (with api_token)
|
|
"""
|
|
self.figma = FigmaIntegration(config)
|
|
|
|
async def execute_tool(self, tool_name: str, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
"""Execute Figma tool"""
|
|
handlers = {
|
|
"figma_get_file": self.figma.get_file,
|
|
"figma_get_styles": self.figma.get_styles,
|
|
"figma_get_components": self.figma.get_components,
|
|
"figma_extract_tokens": self.figma.extract_tokens,
|
|
"figma_get_node": self.figma.get_node
|
|
}
|
|
|
|
handler = handlers.get(tool_name)
|
|
if not handler:
|
|
return {"error": f"Unknown Figma tool: {tool_name}"}
|
|
|
|
try:
|
|
# Remove tool-specific prefix from arguments if needed
|
|
clean_args = {k: v for k, v in arguments.items() if not k.startswith("_")}
|
|
result = await handler(**clean_args)
|
|
return result
|
|
except Exception as e:
|
|
return {"error": str(e)}
|