Skip to main content
Glama

get_theme_info

Retrieve design system theme details including colors, spacing, typography, and breakpoints from Storybook to support design system adoption and refactoring.

Instructions

Get design system theme information (colors, spacing, typography, breakpoints)

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
includeAllNoWhether to include all CSS custom properties found (default: false)

Implementation Reference

  • Main handler function implementing the tool logic: validates input, interacts with Storybook to find theme stories, extracts CSS custom properties (design tokens) from styles and inline styles, categorizes them into theme categories (colors, spacing, typography, breakpoints, shadows, radii), handles 'includeAll' option, returns formatted ThemeInfo response.
    export async function handleGetThemeInfo(input: any) { try { const validatedInput = validateGetThemeInfoInput(input); const includeAll = validatedInput.includeAll || false; const client = new StorybookClient(); // Try to find a theme or foundation story const index = await client.fetchStoriesIndex(); const stories = index.stories || index.entries; if (!stories) { throw new Error('No stories found in Storybook index'); } // Look for theme-related stories const themePatterns = [ /theme/i, /design.*tokens/i, /foundation/i, /colors?/i, /typography/i, /spacing/i, /palette/i, ]; let themeStoryId: string | null = null; for (const [storyId, story] of Object.entries(stories)) { const title = story.title || ''; const name = story.name || story.story || ''; if (themePatterns.some(pattern => pattern.test(title) || pattern.test(name))) { themeStoryId = storyId; break; } } // If no theme story found, try to get any story to extract theme info if (!themeStoryId) { const firstStoryKey = Object.keys(stories)[0]; themeStoryId = firstStoryKey || null; } if (!themeStoryId) { throw new Error('No stories available to extract theme information'); } // Fetch the HTML and styles const componentHTML = await client.fetchComponentHTML(themeStoryId); // Extract all CSS custom properties const allTokens: Record<string, string> = {}; // Extract from styles if (componentHTML.styles) { for (const style of componentHTML.styles) { const tokens = extractDesignTokens(style); tokens.forEach(token => { allTokens[token.name] = token.value; }); } } // Extract from inline styles in HTML const styleRegex = /style="([^"]*)"/g; let match; while ((match = styleRegex.exec(componentHTML.html)) !== null) { if (match[1]) { const inlineStyle = match[1]; const tokens = extractDesignTokens(inlineStyle); tokens.forEach(token => { allTokens[token.name] = token.value; }); } } // Organize tokens into theme categories const theme: ThemeInfo = { colors: {}, spacing: {}, typography: {}, breakpoints: {}, shadows: {}, radii: {}, }; // Categorize tokens for (const [name, value] of Object.entries(allTokens)) { const lowerName = name.toLowerCase(); if ( lowerName.includes('color') || lowerName.includes('bg') || lowerName.includes('text') || lowerName.includes('border') || isColorValue(value) ) { theme.colors[name] = value; } else if ( lowerName.includes('space') || lowerName.includes('spacing') || lowerName.includes('margin') || lowerName.includes('padding') || lowerName.includes('gap') || isSpacingValue(value) ) { theme.spacing[name] = value; } else if ( lowerName.includes('font') || lowerName.includes('text') || lowerName.includes('line') || lowerName.includes('letter') ) { theme.typography[name] = value; } else if ( lowerName.includes('breakpoint') || lowerName.includes('screen') || lowerName.includes('media') ) { theme.breakpoints[name] = value; } else if (lowerName.includes('shadow') || lowerName.includes('elevation')) { theme.shadows[name] = value; } else if (lowerName.includes('radius') || lowerName.includes('rounded')) { theme.radii[name] = value; } else if (includeAll) { // If includeAll is true, add uncategorized tokens to a special category if (!(theme as any).other) { (theme as any).other = {}; } (theme as any).other[name] = value; } } // Add common breakpoint values if not found if (Object.keys(theme.breakpoints).length === 0) { theme.breakpoints = { '--breakpoint-xs': '0px', '--breakpoint-sm': '600px', '--breakpoint-md': '960px', '--breakpoint-lg': '1280px', '--breakpoint-xl': '1920px', }; } const response = { theme, totalTokens: Object.keys(allTokens).length, sourceStoryId: themeStoryId, }; return formatSuccessResponse( response, `Extracted ${Object.keys(allTokens).length} design tokens` ); } catch (error) { return handleError(error); } }
  • Tool definition object including name, description, and inputSchema defining the 'includeAll' boolean parameter.
    export const getThemeInfoTool: Tool = { name: 'get_theme_info', description: 'Get design system theme information (colors, spacing, typography, breakpoints)', inputSchema: { type: 'object', properties: { includeAll: { type: 'boolean', description: 'Whether to include all CSS custom properties found (default: false)', }, }, }, };
  • src/index.ts:15-24 (registration)
    Registration of the tool handler in the toolHandlers Map used for CallToolRequest handling.
    const toolHandlers = new Map<string, (input: any) => Promise<any>>([ ['list_components', tools.handleListComponents], ['get_component_html', tools.handleGetComponentHTML], ['get_component_variants', tools.handleGetComponentVariants], ['search_components', tools.handleSearchComponents], ['get_component_dependencies', tools.handleGetComponentDependencies], ['get_theme_info', tools.handleGetThemeInfo], ['get_component_by_purpose', tools.handleGetComponentByPurpose], ['get_external_css', tools.handleGetExternalCSS], ]);
  • src/index.ts:27-35 (registration)
    Inclusion of the tool in the allTools array returned by ListToolsRequest.
    tools.listComponentsTool, tools.getComponentHTMLTool, tools.getComponentVariantsTool, tools.searchComponentsTool, tools.getComponentDependenciesTool, tools.getThemeInfoTool, tools.getComponentByPurposeTool, tools.getExternalCSSTool, ];
  • Zod schema (GetThemeInfoInputSchema) and validation function (validateGetThemeInfoInput) for input validation, matching the tool's inputSchema.
    const GetThemeInfoInputSchema = z.object({ includeAll: z.boolean().optional(), }); const GetComponentByPurposeInputSchema = z.object({ purpose: z.string(), page: z.number().int().positive().optional(), pageSize: z.number().int().min(1).max(100).optional(), }); const GetComponentCompositionExamplesInputSchema = z.object({ componentId: z.string(), limit: z.number().optional(), }); export function validateListComponentsInput(input: any): ListComponentsInput { const parsed = ListComponentsInputSchema.parse(input); const result: ListComponentsInput = {}; if (parsed.category !== undefined) { result.category = parsed.category; } if (parsed.page !== undefined) { result.page = parsed.page; } if (parsed.pageSize !== undefined) { result.pageSize = parsed.pageSize; } return result; } export function validateGetComponentHTMLInput(input: any): GetComponentHTMLInput { const parsed = GetComponentHTMLInputSchema.parse(input); const result: GetComponentHTMLInput = { componentId: parsed.componentId, }; if (parsed.includeStyles !== undefined) { result.includeStyles = parsed.includeStyles; } return result; } export function validateGetComponentVariantsInput(input: any): GetComponentVariantsInput { return GetComponentVariantsInputSchema.parse(input); } export function validateSearchComponentsInput(input: any): SearchComponentsInput { const parsed = SearchComponentsInputSchema.parse(input); const result: SearchComponentsInput = { query: parsed.query, }; if (parsed.searchIn !== undefined) { result.searchIn = parsed.searchIn; } if (parsed.page !== undefined) { result.page = parsed.page; } if (parsed.pageSize !== undefined) { result.pageSize = parsed.pageSize; } return result; } export function validateGetComponentPropsInput(input: any): GetComponentPropsInput { return GetComponentPropsInputSchema.parse(input); } export function validateGetComponentDependenciesInput(input: any): GetComponentDependenciesInput { return GetComponentDependenciesInputSchema.parse(input); } export function validateGetLayoutComponentsInput(input: any): GetLayoutComponentsInput { const parsed = GetLayoutComponentsInputSchema.parse(input); const result: GetLayoutComponentsInput = {}; if (parsed.includeExamples !== undefined) { result.includeExamples = parsed.includeExamples; } return result; } export function validateGetThemeInfoInput(input: any): GetThemeInfoInput { const parsed = GetThemeInfoInputSchema.parse(input); const result: GetThemeInfoInput = {}; if (parsed.includeAll !== undefined) { result.includeAll = parsed.includeAll; } return result; }

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/freema/mcp-design-system-extractor'

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