extract_tokens_from_css
Extract design tokens by parsing CSS custom properties for use in cross-platform theme generation and WCAG contrast validation.
Instructions
Parse CSS custom properties (variables) and extract design tokens
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| css | Yes | The contents of a CSS file with custom properties |
Implementation Reference
- src/tools/extract-css.ts:7-75 (handler)Main handler function that extracts design tokens from CSS custom properties. Parses CSS variables and categorizes them into colors, typography, spacing, radii, and elevation tokens.
export function extractTokensFromCSS(cssContent: string): DesignTokens { const tokens: DesignTokens = {}; const vars = parseCustomProperties(cssContent); if (vars.size === 0) { throw new Error("No CSS custom properties found in the provided CSS."); } const colors: Record<string, ColorToken> = {}; const typography: Record<string, TypographyToken> = {}; const spacing: Record<string, number> = {}; const radii: Record<string, number> = {}; const elevation: Record<string, ElevationToken> = {}; for (const [name, value] of vars) { const tokenName = name .replace(/^--/, "") .replace(/^(color|clr)-/, "") .replace(/^(font|type|text)-/, "") .replace(/^(space|spacing)-/, "") .replace(/^(radius|radii|rounded)-/, "") .replace(/^(shadow|elevation)-/, ""); // Detect category by prefix if (name.match(/^--(color|clr|bg|fg|text-color|border-color)/)) { const hex = colorToHex(value); if (hex) colors[tokenName] = { value: hex }; } else if (name.match(/^--(font|type|text)/)) { const typo = parseTypographyValue(name, value); if (typo) { const key = tokenName.replace(/^(size|weight|family|height)-?/, ""); if (key) { typography[key] = { ...typography[key], ...typo }; } } } else if (name.match(/^--(space|spacing|gap|padding|margin)/)) { const px = parseToPixels(value); if (px !== null) spacing[tokenName] = px; } else if (name.match(/^--(radius|radii|rounded|border-radius)/)) { const px = parseToPixels(value); if (px !== null) radii[tokenName] = px; } else if (name.match(/^--(shadow|elevation)/)) { const parsed = parseShadowValue(value); if (parsed) elevation[tokenName] = parsed; } else { // Try to auto-detect by value format const hex = colorToHex(value); if (hex) { colors[tokenName] = { value: hex }; } else { const px = parseToPixels(value); if (px !== null) spacing[tokenName] = px; } } } if (Object.keys(colors).length > 0) tokens.colors = colors; if (Object.keys(typography).length > 0) tokens.typography = typography; if (Object.keys(spacing).length > 0) tokens.spacing = spacing; if (Object.keys(radii).length > 0) tokens.radii = radii; if (Object.keys(elevation).length > 0) tokens.elevation = elevation; const hasContent = Object.values(tokens).some((v) => v !== undefined); if (!hasContent) { throw new Error("CSS custom properties found but none could be mapped to design tokens."); } return tokens; } - src/index.ts:56-73 (registration)Tool registration with MCP server. Defines the tool name 'extract_tokens_from_css', input schema (CSS string), and async handler that calls extractTokensFromCSS.
server.registerTool( "extract_tokens_from_css", { description: "Parse CSS custom properties (variables) and extract design tokens", inputSchema: { css: z.string().describe("The contents of a CSS file with custom properties"), }, }, async ({ css }) => { try { const tokens = extractTokensFromCSS(css); return toolResult(JSON.stringify(tokens, null, 2)); } catch (e) { return errorResult(e); } } ); - src/types/tokens.ts:41-48 (schema)DesignTokens interface defining the output schema with optional colors, typography, spacing, radii, elevation, and motion token collections.
export interface DesignTokens { colors?: Record<string, ColorToken>; typography?: Record<string, TypographyToken>; spacing?: Record<string, number>; // px values radii?: Record<string, number>; // px values elevation?: Record<string, ElevationToken>; motion?: Record<string, MotionToken>; } - src/tools/extract-css.ts:81-100 (helper)Helper function parseCustomProperties that extracts CSS custom properties from the document using regex, handling comments and avoiding duplicate declarations.
function parseCustomProperties(css: string): Map<string, string> { const vars = new Map<string, string>(); // Remove comments const cleaned = css.replace(/\/\*[\s\S]*?\*\//g, ""); // Match property declarations: --name: value; const propRegex = /(--[\w-]+)\s*:\s*([^;]+);/g; let match; while ((match = propRegex.exec(cleaned)) !== null) { const name = match[1].trim(); const value = match[2].trim(); // Don't overwrite — first declaration wins (light mode / :root) if (!vars.has(name)) { vars.set(name, value); } } return vars; } - src/tools/extract-css.ts:103-133 (helper)Helper function colorToHex that converts various CSS color formats (hex, rgb, rgba, hsl, hsla) to normalized hex format for token output.
function colorToHex(value: string): string | null { // Already hex if (/^#[0-9a-fA-F]{3,8}$/.test(value)) { return normalizeHex(value); } // rgb(r, g, b) or rgba(r, g, b, a) const rgbMatch = value.match( /rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*[\d.]+)?\s*\)/ ); if (rgbMatch) { const r = parseInt(rgbMatch[1]).toString(16).padStart(2, "0"); const g = parseInt(rgbMatch[2]).toString(16).padStart(2, "0"); const b = parseInt(rgbMatch[3]).toString(16).padStart(2, "0"); return `#${r}${g}${b}`.toUpperCase(); } // hsl(h, s%, l%) — approximate conversion const hslMatch = value.match( /hsla?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)%\s*,\s*(\d+(?:\.\d+)?)%/ ); if (hslMatch) { return hslToHex( parseFloat(hslMatch[1]), parseFloat(hslMatch[2]), parseFloat(hslMatch[3]) ); } return null; }