Skip to main content
Glama
kenneives

design-token-bridge-mcp

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
NameRequiredDescriptionDefault
cssYesThe contents of a CSS file with custom properties

Implementation Reference

  • 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);
        }
      }
    );
  • 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>;
    }
  • 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;
    }
  • 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;
    }
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries full burden but only states the basic operation. It doesn't disclose behavioral traits like whether parsing is strict or lenient, what format extracted tokens are in, error handling for invalid CSS, or performance characteristics. For a parsing tool with zero annotation coverage, this is insufficient.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, efficient sentence with zero wasted words. It's front-loaded with the core purpose and appropriately sized for this simple tool.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given no annotations, no output schema, and a parsing operation that likely returns structured data, the description is incomplete. It doesn't explain what 'design tokens' means in this context, what format they're returned in, or any limitations. The simplicity of the tool (1 parameter) doesn't compensate for these gaps.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100% (the 'css' parameter is fully documented in schema), so baseline is 3. The description adds no additional parameter semantics beyond what the schema already provides about CSS file contents with custom properties.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the specific action ('Parse CSS custom properties and extract design tokens'), identifies the resource (CSS files with custom properties), and distinguishes from siblings by focusing on CSS input rather than Figma, JSON, Tailwind, or generation/validation tools.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies usage context (when you have CSS with custom properties to extract tokens), but doesn't explicitly state when NOT to use it or name alternatives like 'extract_tokens_from_json' for non-CSS sources. The sibling tools provide clear alternatives, but the description doesn't reference them.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/kenneives/design-token-bridge-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server