UNPKG

@spaced-out/ui-design-system

Version:
1,584 lines (1,428 loc) 60.1 kB
#!/usr/bin/env node /** * Genesis UI Design System MCP Server * * Provides AI assistants with access to the Genesis UI Design System through MCP. * This version reads from pre-bundled JSON data. * * Usage with any MCP client: * { * "mcpServers": { * "genesis-design-system": { * "command": "npx", * "args": ["-y", "@spaced-out/genesis-mcp@latest"] * } * } * } */ import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, } from '@modelcontextprotocol/sdk/types.js'; import { readFileSync } from 'fs'; import { join, dirname } from 'path'; import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); // Load pre-bundled design system data let designSystemData; try { const dataPath = join(__dirname, 'data', 'design-system.json'); designSystemData = JSON.parse(readFileSync(dataPath, 'utf-8')); } catch (error) { console.error('❌ ERROR: Could not load design system data'); console.error(' Make sure to run "npm run build" before using this server'); console.error(` Error: ${error.message}`); process.exit(1); } // Log successful load // note(vish): Using console.error() because stdout is reserved for MCP protocol messages. // All logging/debugging output must go to stderr to avoid corrupting the protocol. console.error('✅ Loaded Genesis design system data'); console.error(` Version: ${designSystemData.metadata.version}`); console.error(` Components: ${Object.keys(designSystemData.components).length}`); console.error(` Hooks: ${Object.keys(designSystemData.hooks).length}`); console.error(` Utils: ${Object.keys(designSystemData.utils || {}).length}`); console.error(` Types: ${Object.keys(designSystemData.types || {}).length}`); console.error(` Built: ${designSystemData.metadata.buildDate}`); /** * Get all components */ function getAllComponents() { return Object.keys(designSystemData.components).sort(); } /** * Get all hooks */ function getAllHooks() { return Object.keys(designSystemData.hooks).sort(); } /** * Get all utils */ function getAllUtils() { return Object.keys(designSystemData.utils || {}).sort(); } /** * Get all types */ function getAllTypes() { return Object.keys(designSystemData.types || {}).sort(); } /** * Get component details */ function getComponentDetails(componentName) { return designSystemData.components[componentName] || null; } /** * Get hook details */ function getHookDetails(hookName) { return designSystemData.hooks[hookName] || null; } /** * Get util details */ function getUtilDetails(utilName) { return designSystemData.utils?.[utilName] || null; } /** * Get type details */ function getTypeDetails(typeName) { return designSystemData.types?.[typeName] || null; } /** * Get all design tokens */ function getAllDesignTokens() { return designSystemData.tokens; } /** * Search components by name or content */ function searchComponents(query) { const components = getAllComponents(); const results = []; const lowerQuery = query.toLowerCase(); for (const componentName of components) { if (componentName.toLowerCase().includes(lowerQuery)) { const details = getComponentDetails(componentName); let description = ''; if (details.files.story?.content) { const storyContent = details.files.story.content; const descMatch = storyContent.match(/description:\s*{[\s\S]*?component:\s*`([\s\S]*?)`/); description = descMatch ? descMatch[1].trim().split('\n')[0] : ''; } results.push({ name: componentName, description, hasStory: !!details.files.story, hasCSS: !!details.files.css, path: details.path, }); } } return results; } /** * Get CSS Module styling guidelines */ function getCSSModuleGuidelines() { return { description: 'Comprehensive CSS Module guidelines and best practices for the Genesis Design System', contextNote: { critical: '⚠️ IMPORTANT: Patterns differ based on context', insideDesignSystem: 'Use .module.css files with PascalCase naming and relative token imports', usingDesignSystem: 'Use .css files (no .module) with kebab-case naming and @spaced-out/ui-design-system imports', recommendation: 'Call get_css_module_import_patterns tool for detailed context-specific examples', }, typescriptRequirement: '⚠️ CRITICAL: Use pure TypeScript only. NO Flow types (Flow.AbstractComponent). Use React.forwardRef<HTMLElement, Props> pattern.', cssModuleImportPattern: { critical: '🚨 MANDATORY: ALWAYS import CSS Modules as a default import with "css" as the variable name', insideDesignSystem: 'import css from \'./ComponentName.module.css\';', usingDesignSystem: 'import css from \'./component-name.css\';', incorrect: [ 'import \'./ComponentName.module.css\'; // ❌ WRONG - No side-effect imports', 'import * as css from \'./ComponentName.module.css\'; // ❌ WRONG - No namespace imports', 'import styles from \'./ComponentName.module.css\'; // ❌ WRONG - Must use "css" as variable name', ], classNameUsage: { correct: 'className={css.tableHeaderContent} // Use camelCase property access', incorrect: [ 'className="table-header-content" // ❌ WRONG - No string literals', 'className="tableHeaderContent" // ❌ WRONG - No string literals', 'className={\'table-header-content\'} // ❌ WRONG - No string literals in braces', ], }, explanation: 'CSS Modules require a default import to access class names as properties. The variable MUST be named "css" for consistency across the codebase. Class names are accessed as camelCase properties (css.myClass) not as string literals.', }, criticalRules: [ '🚨 MANDATORY: Import CSS Modules as: import css from \'./Component.module.css\'', '🚨 MANDATORY: Use className={css.className} NOT className="class-name"', '⚠️ ALWAYS use design tokens instead of hardcoded values', '⚠️ Use BEM modifier pattern: .baseClass.modifier NOT .baseClass--modifier', '⚠️ Use descendant selectors for child element styling: .container .childClass NOT :global() selectors', '⚠️ Prefer using size tokens (size58, size34) over hardcoded pixel values', '⚠️ Import tokens from ../../styles/variables/_<category>.css', ], classNamingConventions: { description: 'Follow BEM-inspired patterns with CSS Modules', correct: [ '.wrapper { } // Base class', '.header { } // Element', '.header.isOpen { } // State modifier using additional class', '.panel.active { } // State modifier', '.container.warning { } // Theme/variant modifier', '.container .warning { } // Descendant element styling', ], incorrect: [ '.header--isOpen { } // ❌ Don\'t use double dash for modifiers', '.panel--active { } // ❌ Use single class modifier instead', '.container--warning { } // ❌ Use .container.warning', '.container--warning :global(.child) { } // ❌ Avoid :global, use .container .warning', ], reasoning: 'CSS Modules provide scoping, so we can use simpler class names. The double-dash BEM syntax is less necessary and adds verbosity. Use single dot for modifiers and descendant selectors for child elements.', }, tokenUsage: { description: 'Always prefer design tokens over hardcoded values', sizeTokens: { available: ['size12', 'size16', 'size18', 'size20', 'size24', 'size28', 'size32', 'size34', 'size40', 'size48', 'size58', 'size60', 'sizeFluid'], usage: 'Use for widths, heights, min-widths, min-heights, icon sizes, etc.', correct: 'width: size58; height: size34;', incorrect: 'width: 58px; height: 34px;', }, spaceTokens: { available: ['spaceNone', 'spaceXSmall', 'spaceSmall', 'spaceMedium', 'spaceLarge', 'spaceXLarge'], usage: 'Use for padding, margin, gaps', correct: 'padding: spaceSmall spaceMedium;', incorrect: 'padding: 12px 20px;', }, borderTokens: { available: ['borderRadiusNone', 'borderRadiusSmall', 'borderRadiusMedium', 'borderRadiusLarge', 'borderWidthNone', 'borderWidthPrimary', 'borderWidthSecondary', 'borderWidthTertiary'], usage: 'Use for border-radius, border-width', correct: 'border: borderWidthPrimary solid colorBorderPrimary;', incorrect: 'border: 1px solid #E1E1E1; border-radius: 116px;', }, colorTokens: { description: 'Use semantic color tokens, never hex values', correct: 'background-color: colorInformationLightest; color: colorInformation;', incorrect: 'background: #e6f0fe; color: #0769f0;', }, }, stateStylingPattern: { description: 'How to apply state-based styling to containers and their children', pattern: `// Base container .container { display: flex; padding: spaceMedium; border-radius: borderRadiusSmall; align-items: center; justify-content: center; } // Container background/border for each state .container.information { background-color: colorInformationLightest; border-color: colorInformation; } .container.warning { background-color: colorWarningLightest; border-color: colorWarning; } // Child element styling using descendant selectors .container .information { color: colorInformation; } .container .warning { color: colorWarning; }`, explanation: 'This pattern separates container state styling from child element styling. Apply state classes to both the container and child elements, then use descendant selectors to apply appropriate colors/styles.', useCases: [ 'Icon colors within state containers (alerts, badges, cards)', 'Text colors within status panels', 'Button or link colors within themed sections', 'Any child element that should inherit state-based styling', ], doNot: 'Avoid using :global() selector unless absolutely necessary for targeting third-party components you don\'t control', }, compositionPattern: { description: 'Leverage CSS Modules composition for reusability', example: `.header { composes: subTitleSmall from '../../styles/typography.module.css'; composes: borderPrimary from '../../styles/border.module.css'; // Additional styles... }`, availableCompositions: [ 'Typography: bodySmall, bodyMedium, bodyLarge, subTitleSmall, subTitleMedium, etc. from typography.module.css', 'Borders: borderPrimary, borderSecondary, borderTopPrimary, borderBottomPrimary from border.module.css', ], }, realWorldExample: { description: 'Complete example with state-based styling for multiple child elements', importPattern: `// 🚨 MANDATORY: Import CSS Module as default with "css" variable name import css from './ComponentName.module.css'; // Usage in JSX: <div className={css.wrapper}> <div className={css.header}> <span className={css.title}>Title</span> </div> </div> // ❌ NEVER DO THIS: // import './ComponentName.module.css'; // <div className="wrapper"> // <div className={'wrapper'}>`, code: `@value ( colorBorderPrimary, colorBackgroundTertiary, colorNeutralLightest, colorNeutral, colorInformationLightest, colorInformation, colorTextPrimary ) from '../../styles/variables/_color.css'; @value (sizeFluid, size58, size34) from '../../styles/variables/_size.css'; @value (spaceSmall, spaceMedium, spaceXSmall) from '../../styles/variables/_space.css'; @value (borderRadiusSmall, borderWidthPrimary) from '../../styles/variables/_border.css'; .wrapper { display: flex; flex-flow: column; width: sizeFluid; } .header { composes: subTitleSmall from '../../styles/typography.module.css'; min-height: size58; border-radius: borderRadiusSmall; border: borderWidthPrimary solid colorBorderPrimary; display: flex; align-items: center; padding: spaceSmall spaceMedium; background-color: colorBackgroundTertiary; } /* State container - applies background */ .statusPanel { display: flex; gap: spaceSmall; padding: spaceMedium; border-radius: borderRadiusSmall; border: borderWidthPrimary solid; } .statusPanel.neutral { background-color: colorNeutralLightest; border-color: colorNeutral; } .statusPanel.information { background-color: colorInformationLightest; border-color: colorInformation; } /* Child elements - use descendant selectors for colors */ .statusPanel .neutral { color: colorNeutral; } .statusPanel .information { color: colorInformation; } /* Works for text, icons, or any child element */ .statusText { composes: bodySmall from '../../styles/typography.module.css'; }`, }, commonMistakes: [ { mistake: '🚨 CRITICAL: Incorrect CSS Module import or usage', wrong: `import './Component.module.css'; // Side-effect import import styles from './Component.module.css'; // Wrong variable name <div className="wrapper"> // String literal <div className={'wrapper'}> // String literal in braces`, right: `import css from './Component.module.css'; // Default import with "css" variable <div className={css.wrapper}> // Property access with camelCase`, explanation: 'CSS Modules MUST be imported as default with variable name "css" and used via property access, never as string literals.', }, { mistake: 'Using hardcoded pixel values', wrong: 'width: 58px; height: 78px; padding: 12px 16px;', right: 'width: size58; height: size58; padding: spaceSmall spaceMedium;', }, { mistake: 'Using hex colors directly', wrong: 'background: #fcf8e7; color: #0769f0; border-color: #e1e1e1;', right: 'background: colorWarningLightest; color: colorInformation; border-color: colorBorderPrimary;', }, { mistake: 'Using BEM double-dash with CSS Modules', wrong: '.container--active { } .panel--warning { } .button--disabled { }', right: '.container.active { } .panel.warning { } .button.disabled { }', }, { mistake: 'Using :global() unnecessarily for child elements', wrong: '.container--warning :global(svg) { color: orange; } .alert--error :global(.text) { color: red; }', right: '.container .warning { color: colorWarning; } .alert .error { color: colorDanger; }', }, { mistake: 'Not grouping token imports by category', wrong: '@value (colorPrimary, spaceMedium, colorSecondary, borderRadius) from \'../../styles/variables/...\'', right: 'Import each category from its own file: colors from _color.css, spacing from _space.css, etc.', }, ], }; } /** * Get CSS Module import and usage patterns */ function getCSSModuleImportPatterns() { return { title: '🚨 MANDATORY CSS Module Import and Usage Pattern', critical: 'This pattern is REQUIRED but differs based on context: developing INSIDE the design system vs USING the design system in another app.', contextWarning: { title: '⚠️ CRITICAL: Two Different Contexts', description: 'The patterns differ significantly based on where you are developing', contexts: { insideDesignSystem: 'Building components IN the ui-design-system repository', usingDesignSystem: 'Building features in an app that USES @spaced-out/ui-design-system', }, howToKnow: 'Check package.json: If it has "name": "@spaced-out/ui-design-system", you are INSIDE. If it has "@spaced-out/ui-design-system" as a dependency, you are USING it.', }, insideDesignSystem: { title: '📦 Context: Developing INSIDE the Design System Repository', fileNaming: { tsx: 'ComponentName.tsx (PascalCase)', css: 'ComponentName.module.css (PascalCase + .module.css)', examples: ['DataTable.tsx + DataTable.module.css', 'UserProfile.tsx + UserProfile.module.css'], }, cssImport: { pattern: "import css from './ComponentName.module.css';", examples: [ "import css from './DataTable.module.css';", "import css from './UserProfile.module.css';", ], }, tokenImports: { pattern: "@value (tokenName) from '../../styles/variables/_category.css';", explanation: 'Use RELATIVE paths to the styles directory', examples: [ "@value (size480) from '../../styles/variables/_size.css';", "@value (colorPrimary) from '../../styles/variables/_color.css';", "@value (spaceMedium) from '../../styles/variables/_space.css';", ], }, componentImports: { pattern: "from 'src/components/ComponentName';", examples: [ "import {Button} from 'src/components/Button';", "import {Table} from 'src/components/Table';", ], }, utilImports: { pattern: "from 'src/utils/utilName';", examples: [ "import classify from 'src/utils/classify';", "import {generateTestId} from 'src/utils/qa';", ], }, }, usingDesignSystem: { title: '🏗️ Context: Using Design System in Another Application', fileNaming: { tsx: 'component-name.tsx (kebab-case)', css: 'component-name.css (kebab-case, NO .module.css suffix)', examples: ['data-table.tsx + data-table.css', 'user-profile.tsx + user-profile.css'], critical: '🚨 File is .css NOT .module.css', }, cssImport: { pattern: "import css from './component-name.css';", examples: [ "import css from './data-table.css';", "import css from './user-profile.css';", ], note: 'Notice: NO .module.css suffix in the filename', }, tokenImports: { pattern: "@value (tokenName) from '@spaced-out/ui-design-system/lib/styles/variables/_category.css';", explanation: 'Use NPM PACKAGE path, not relative paths', examples: [ "@value (size480) from '@spaced-out/ui-design-system/lib/styles/variables/_size.css';", "@value (colorPrimary) from '@spaced-out/ui-design-system/lib/styles/variables/_color.css';", "@value (spaceMedium) from '@spaced-out/ui-design-system/lib/styles/variables/_space.css';", ], critical: '🚨 Must use @spaced-out/ui-design-system/lib/styles/... NOT relative paths', }, componentImports: { pattern: "from '@spaced-out/ui-design-system/lib/components/ComponentName';", examples: [ "import {Button} from '@spaced-out/ui-design-system/lib/components/Button';", "import {Table} from '@spaced-out/ui-design-system/lib/components/Table';", ], }, utilImports: { pattern: "from '@spaced-out/ui-design-system/lib/utils/utilName';", examples: [ "import classify from '@spaced-out/ui-design-system/lib/utils/classify';", "import {generateTestId} from '@spaced-out/ui-design-system/lib/utils/qa';", ], }, }, comparisonTable: { title: 'Side-by-Side Comparison', aspect: { tsxFileName: { insideDS: 'ComponentName.tsx (PascalCase)', usingDS: 'component-name.tsx (kebab-case)', }, cssFileName: { insideDS: 'ComponentName.module.css', usingDS: 'component-name.css (NO .module)', }, cssImport: { insideDS: "import css from './ComponentName.module.css';", usingDS: "import css from './component-name.css';", }, tokenImport: { insideDS: "@value (size480) from '../../styles/variables/_size.css';", usingDS: "@value (size480) from '@spaced-out/ui-design-system/lib/styles/variables/_size.css';", }, componentImport: { insideDS: "import {Button} from 'src/components/Button';", usingDS: "import {Button} from '@spaced-out/ui-design-system/lib/components/Button';", }, }, }, correctImport: { pattern: 'import css from \'./ComponentName.module.css\';', explanation: 'CSS Modules MUST be imported as a default import with the variable name "css"', examples: [ 'import css from \'./DataTable.module.css\';', 'import css from \'./UserProfile.module.css\';', 'import css from \'./NavigationBar.module.css\';', ], }, incorrectImports: { sideEffect: { code: 'import \'./ComponentName.module.css\';', error: '❌ WRONG: Side-effect imports do not provide access to class names', why: 'This imports the CSS but you cannot access the class names in your component', }, namespace: { code: 'import * as css from \'./ComponentName.module.css\';', error: '❌ WRONG: Namespace imports are not supported for CSS Modules', why: 'CSS Modules use default exports, not named exports', }, wrongVariableName: { code: 'import styles from \'./ComponentName.module.css\';', error: '❌ WRONG: Variable name must be "css" not "styles" or any other name', why: 'Consistency across the codebase requires using "css" as the variable name', }, }, correctUsage: { pattern: 'className={css.className}', explanation: 'Class names are accessed as camelCase properties of the css object', examples: [ '<div className={css.wrapper}>', '<div className={css.tableHeader}>', '<button className={css.submitButton}>', '<span className={css.errorMessage}>', ], withClassify: [ 'className={classify(css.wrapper, css.active)}', 'className={classify(css.button, { [css.disabled]: isDisabled })}', 'className={classify(css.container, classNames?.wrapper)}', ], }, incorrectUsage: { stringLiteral: { code: '<div className="wrapper">', error: '❌ WRONG: Never use string literals for class names', why: 'CSS Modules provide scoped class names that must be accessed via the css object', }, stringInBraces: { code: '<div className={\'wrapper\'}>', error: '❌ WRONG: String literals in braces are still wrong', why: 'You must use the css object property access: className={css.wrapper}', }, kebabCase: { code: '<div className={css[\'table-header\']}>', error: '❌ WRONG: Use camelCase properties, not kebab-case strings', why: 'CSS class names are automatically converted to camelCase properties', }, }, cssFileNaming: { inDesignSystem: { pattern: 'ComponentName.module.css', examples: [ 'DataTable.module.css', 'UserProfile.module.css', 'NavigationBar.module.css', ], }, inMainApp: { pattern: 'component-name.module.css (kebab-case)', examples: [ 'data-table.module.css', 'user-profile.module.css', 'navigation-bar.module.css', ], note: 'The main application uses kebab-case for file names', }, }, cssSyntax: { classDefinition: 'Use camelCase for class names in CSS files', correct: [ '.wrapper { }', '.tableHeader { }', '.submitButton { }', '.errorMessage { }', ], incorrect: [ '.table-header { } // ❌ Use .tableHeader instead', '.submit_button { } // ❌ Use .submitButton instead', ], }, completeExamples: { insideDesignSystem: { title: 'Complete Example: Building INSIDE Design System', tsx: `// DataTable.tsx (in ui-design-system repo) import * as React from 'react'; import classify from 'src/utils/classify'; import {generateTestId} from 'src/utils/qa'; import {Button} from 'src/components/Button'; import {SearchInput} from 'src/components/SearchInput'; // 📦 INSIDE DESIGN SYSTEM: Import with .module.css and relative path import css from './DataTable.module.css'; export const DataTable = () => ( <div className={css.wrapper}> <div className={css.header}> <SearchInput className={css.search} /> <Button>Filter</Button> </div> <table className={css.table}> <thead className={css.thead}> <tr> <th className={css.th}>Name</th> <th className={css.th}>Status</th> </tr> </thead> </table> </div> );`, css: `/* DataTable.module.css (in ui-design-system repo) */ /* 📦 INSIDE DESIGN SYSTEM: Import tokens with RELATIVE paths */ @value ( colorBorderPrimary, colorBackgroundTertiary ) from '../../styles/variables/_color.css'; @value ( spaceSmall, spaceMedium ) from '../../styles/variables/_space.css'; @value (size480) from '../../styles/variables/_size.css'; .wrapper { display: flex; flex-direction: column; width: size480; } .header { padding: spaceMedium; background: colorBackgroundTertiary; } .table { border: 1px solid colorBorderPrimary; }`, }, usingDesignSystem: { title: 'Complete Example: USING Design System in Another App', tsx: `// data-table.tsx (in your app repo, NOT in ui-design-system) import * as React from 'react'; import classify from '@spaced-out/ui-design-system/lib/utils/classify'; import {generateTestId} from '@spaced-out/ui-design-system/lib/utils/qa'; import {Button} from '@spaced-out/ui-design-system/lib/components/Button'; import {SearchInput} from '@spaced-out/ui-design-system/lib/components/SearchInput'; // 🏗️ USING DESIGN SYSTEM: Import WITHOUT .module.css, kebab-case filename import css from './data-table.css'; export const DataTable = () => ( <div className={css.wrapper}> <div className={css.header}> <SearchInput className={css.search} /> <Button>Filter</Button> </div> <table className={css.table}> <thead className={css.thead}> <tr> <th className={css.th}>Name</th> <th className={css.th}>Status</th> </tr> </thead> </table> </div> );`, css: `/* data-table.css (in your app repo, NOT in ui-design-system) */ /* 🏗️ USING DESIGN SYSTEM: Import tokens from NPM PACKAGE path */ @value ( colorBorderPrimary, colorBackgroundTertiary ) from '@spaced-out/ui-design-system/lib/styles/variables/_color.css'; @value ( spaceSmall, spaceMedium ) from '@spaced-out/ui-design-system/lib/styles/variables/_space.css'; @value (size480) from '@spaced-out/ui-design-system/lib/styles/variables/_size.css'; .wrapper { display: flex; flex-direction: column; width: size480; } .header { padding: spaceMedium; background: colorBackgroundTertiary; } .table { border: 1px solid colorBorderPrimary; }`, }, }, }; } /** * Get design token import guidelines */ function getDesignTokenImportGuidelines() { return { description: 'Guidelines for importing design tokens in CSS Module files', importPattern: 'All design tokens must be imported from their respective CSS variable files in src/styles/variables/', availableTokenFiles: [ { file: '../../styles/variables/_color.css', description: 'Color tokens for text, backgrounds, borders, icons, etc.', exampleTokens: ['colorTextPrimary', 'colorBackgroundPrimary', 'colorBorderPrimary', 'colorIconPrimary'], }, { file: '../../styles/variables/_space.css', description: 'Spacing tokens for padding, margin, gaps, etc.', exampleTokens: ['spaceXSmall', 'spaceSmall', 'spaceMedium', 'spaceLarge', 'spaceXLarge'], }, { file: '../../styles/variables/_border.css', description: 'Border tokens for border-radius and border-width', exampleTokens: ['borderRadiusSmall', 'borderRadiusMedium', 'borderRadiusLarge', 'borderWidthPrimary'], }, { file: '../../styles/variables/_size.css', description: 'Size tokens for dimensions', exampleTokens: ['sizeSmall', 'sizeMedium', 'sizeLarge'], }, { file: '../../styles/variables/_font.css', description: 'Font tokens for typography', exampleTokens: ['fontFamilyPrimary', 'fontSizeSmall', 'fontSizeMedium', 'fontWeightRegular'], }, { file: '../../styles/variables/_shadow.css', description: 'Shadow tokens for box-shadow', exampleTokens: ['shadowSmall', 'shadowMedium', 'shadowLarge'], }, { file: '../../styles/variables/_elevation.css', description: 'Elevation tokens for layering', exampleTokens: ['elevationLow', 'elevationMedium', 'elevationHigh'], }, { file: '../../styles/variables/_motion.css', description: 'Motion tokens for animations and transitions', exampleTokens: ['durationFast', 'durationNormal', 'durationSlow', 'easingStandard'], }, { file: '../../styles/variables/_opacity.css', description: 'Opacity tokens', exampleTokens: ['opacityDisabled', 'opacityMedium'], }, ], correctExample: `@value ( colorBackgroundTertiary, colorBorderPrimary, colorTextPrimary ) from '../../styles/variables/_color.css'; @value ( borderRadiusLarge, borderWidthPrimary ) from '../../styles/variables/_border.css'; @value ( spaceSmall, spaceMedium ) from '../../styles/variables/_space.css'; .wrapper { background: colorBackgroundTertiary; border: borderWidthPrimary solid colorBorderPrimary; border-radius: borderRadiusLarge; padding: spaceMedium; }`, incorrectExample: `/* ❌ WRONG - Do not import from 'ui-design-system' */ @value ( colorBorderPrimary, spaceSmall ) from 'ui-design-system';`, notes: [ 'Always use the relative path from the component directory: ../../styles/variables/_<category>.css', 'Group imports by token category (color, space, border, etc.)', 'Only import the tokens you actually use in your component', 'Use CSS Modules @value syntax for importing tokens', ], }; } /** * Extract exported types from component code */ function extractExportedTypes(componentCode) { if (!componentCode) return []; const exports = []; // Match: export type TypeName = ... const typeExportRegex = /export\s+type\s+(\w+)\s*=\s*([^;]+);/g; let match; while ((match = typeExportRegex.exec(componentCode)) !== null) { exports.push({ kind: 'type', name: match[1], definition: match[2].trim(), raw: match[0], }); } // Match: export interface InterfaceName { ... } const interfaceExportRegex = /export\s+interface\s+(\w+)(?:<[^>]+>)?\s*{/g; while ((match = interfaceExportRegex.exec(componentCode)) !== null) { exports.push({ kind: 'interface', name: match[1], definition: 'interface', raw: match[0], }); } // Match: export const CONSTANT_NAME = Object.freeze({ ... }) const constExportRegex = /export\s+const\s+([A-Z_]+)\s*=\s*Object\.freeze\s*\(\s*{([^}]+)}\s*\)/g; while ((match = constExportRegex.exec(componentCode)) !== null) { exports.push({ kind: 'const', name: match[1], definition: `Object.freeze({${match[2]}})`, raw: match[0], }); } // Match: export enum EnumName { ... } const enumExportRegex = /export\s+enum\s+(\w+)\s*{/g; while ((match = enumExportRegex.exec(componentCode)) !== null) { exports.push({ kind: 'enum', name: match[1], definition: 'enum', raw: match[0], }); } return exports; } /** * Get component template for onboarding * * IMPORTANT: This template uses pure TypeScript patterns. * DO NOT use Flow types (Flow.AbstractComponent) in new components. */ function getComponentTemplate() { return { 'ComponentName.tsx': `import * as React from 'react'; import classify from 'src/utils/classify'; import {generateTestId} from 'src/utils/qa'; // 🚨 CRITICAL: CSS Modules MUST be imported as default import with variable name "css" // ✅ Correct: import css from './ComponentName.module.css'; // ❌ WRONG: import './ComponentName.module.css'; // ❌ WRONG: import * as css from './ComponentName.module.css'; // ❌ WRONG: import styles from './ComponentName.module.css'; import css from 'src/components/ComponentName/ComponentName.module.css'; // IMPORTANT: Use pure TypeScript - NO Flow types // ✅ Correct: React.forwardRef<HTMLDivElement, Props> // ❌ Wrong: Flow.AbstractComponent<Props, HTMLDivElement> // IMPORTANT: Use className with CSS Module property access // ✅ Correct: className={css.wrapper} // ❌ WRONG: className="wrapper" // ❌ WRONG: className={'wrapper'} type ClassNames = Readonly<{ wrapper?: string; }>; export interface ComponentNameProps { classNames?: ClassNames; children?: React.ReactNode; testId?: string; } export const ComponentName = React.forwardRef<HTMLDivElement, ComponentNameProps>( ( { classNames, children, testId, ...props }, ref, ) => ( <div {...props} ref={ref} className={classify(css.wrapper, classNames?.wrapper)} data-testid={generateTestId({base: testId, slot: 'root'})} > {children} </div> ), ); ComponentName.displayName = 'ComponentName'; `, 'ComponentName.module.css': `@value ( colorBorderPrimary, colorBackgroundPrimary ) from '../../styles/variables/_color.css'; @value ( spaceSmall, spaceMedium ) from '../../styles/variables/_space.css'; @value ( borderRadiusSmall ) from '../../styles/variables/_border.css'; .wrapper { padding: spaceMedium; background: colorBackgroundPrimary; border: 1px solid colorBorderPrimary; border-radius: borderRadiusSmall; } `, 'ComponentName.stories.tsx': `import * as React from 'react'; import type {ComponentNameProps} from 'src/components/ComponentName/ComponentName'; import {ComponentName} from 'src/components/ComponentName/ComponentName'; export default { tags: ['autodocs'], title: 'Components/ComponentName', component: ComponentName, argTypes: { children: { description: 'The content of the component', table: { type: {summary: 'React.ReactNode'}, }, }, classNames: { description: 'External classNames to be applied', control: { type: 'object', }, table: { type: {summary: '{wrapper?: string}'}, }, }, testId: { description: 'Test ID for the component', control: { type: 'text', }, table: { type: {summary: 'string'}, }, }, }, parameters: { docs: { subtitle: 'Generates a ComponentName component', description: { component: \` \\\`\\\`\\\`js import { ComponentName } from "@spaced-out/ui-design-system/lib/components/ComponentName"; \\\`\\\`\\\` Brief description of what this component does and when to use it. \`, }, }, storySource: { componentPath: '/src/components/ComponentName/ComponentName', }, }, }; export const DefaultStory = (args: ComponentNameProps) => ( <ComponentName {...args} /> ); DefaultStory.args = { children: 'ComponentName content', testId: 'component-name-default', }; DefaultStory.storyName = 'Default'; `, 'index.ts': `export {ComponentName} from 'src/components/ComponentName/ComponentName'; export type {ComponentNameProps} from 'src/components/ComponentName/ComponentName'; `, }; } /** * Analyze component dependencies */ function analyzeComponentDependencies(componentName) { const details = getComponentDetails(componentName); if (!details || !details.files.main) { return null; } // Extract imports from the component const content = details.files.main.content; const importRegex = /import\s+.*?from\s+['"](.+?)['"]/g; const imports = []; let match; while ((match = importRegex.exec(content)) !== null) { imports.push(match[1]); } // Categorize imports const dependencies = { components: imports.filter(imp => imp.includes('src/components/')), hooks: imports.filter(imp => imp.includes('src/hooks/')), utils: imports.filter(imp => imp.includes('src/utils/')), styles: imports.filter(imp => imp.includes('src/styles/')), external: imports.filter(imp => !imp.startsWith('src/')), }; return dependencies; } /** * Initialize MCP Server */ const server = new Server( { name: 'genesis-design-system', version: designSystemData.metadata.version, }, { capabilities: { resources: {}, tools: {}, }, } ); /** * List available resources */ server.setRequestHandler(ListResourcesRequestSchema, async () => { const components = getAllComponents(); const hooks = getAllHooks(); const utils = getAllUtils(); const types = getAllTypes(); const resources = [ { uri: 'genesis://overview', name: 'Design System Overview', mimeType: 'text/markdown', description: 'Overview of the Genesis UI Design System', }, { uri: 'genesis://components', name: 'All Components', mimeType: 'application/json', description: `List of all ${components.length} available components`, }, { uri: 'genesis://hooks', name: 'All Hooks', mimeType: 'application/json', description: `List of all ${hooks.length} available hooks`, }, { uri: 'genesis://utils', name: 'All Utils', mimeType: 'application/json', description: `List of all ${utils.length} available utility modules`, }, { uri: 'genesis://types', name: 'All Types', mimeType: 'application/json', description: `List of all ${types.length} available type definitions`, }, { uri: 'genesis://tokens', name: 'Design Tokens', mimeType: 'application/json', description: 'All design tokens (colors, spacing, typography, etc.)', }, ]; // Add individual component resources components.forEach(component => { resources.push({ uri: `genesis://component/${component}`, name: `Component: ${component}`, mimeType: 'text/markdown', description: `Documentation and code for ${component} component`, }); }); // Add individual hook resources hooks.forEach(hook => { resources.push({ uri: `genesis://hook/${hook}`, name: `Hook: ${hook}`, mimeType: 'text/markdown', description: `Documentation and code for ${hook} hook`, }); }); // Add individual util resources utils.forEach(util => { resources.push({ uri: `genesis://util/${util}`, name: `Util: ${util}`, mimeType: 'text/markdown', description: `Utility functions for ${util}`, }); }); // Add individual type resources types.forEach(type => { resources.push({ uri: `genesis://type/${type}`, name: `Type: ${type}`, mimeType: 'text/markdown', description: `Type definitions for ${type}`, }); }); return { resources }; }); /** * Read resource content */ server.setRequestHandler(ReadResourceRequestSchema, async (request) => { const uri = request.params.uri; if (uri === 'genesis://overview') { const overview = `# Genesis UI Design System **Version:** ${designSystemData.metadata.version} **Last Built:** ${new Date(designSystemData.metadata.buildDate).toLocaleString()} ## Statistics - **Components:** ${Object.keys(designSystemData.components).length} - **Hooks:** ${Object.keys(designSystemData.hooks).length} - **Utils:** ${Object.keys(designSystemData.utils || {}).length} - **Types:** ${Object.keys(designSystemData.types || {}).length} - **Design Token Categories:** ${Object.keys(designSystemData.tokens).length} ## Available Components ${getAllComponents().map(c => `- ${c}`).join('\n')} ## Available Hooks ${getAllHooks().map(h => `- ${h}`).join('\n')} ## Available Utils ${getAllUtils().map(u => `- ${u}`).join('\n')} ## Available Types ${getAllTypes().map(t => `- ${t}`).join('\n')} ## Design Token Categories ${Object.keys(designSystemData.tokens).map(cat => `- **${cat}**: ${Object.keys(designSystemData.tokens[cat]).length} file(s)`).join('\n')} `; return { contents: [ { uri, mimeType: 'text/markdown', text: overview, }, ], }; } if (uri === 'genesis://components') { const components = getAllComponents(); return { contents: [ { uri, mimeType: 'application/json', text: JSON.stringify(components, null, 2), }, ], }; } if (uri === 'genesis://hooks') { const hooks = getAllHooks(); return { contents: [ { uri, mimeType: 'application/json', text: JSON.stringify(hooks, null, 2), }, ], }; } if (uri === 'genesis://utils') { const utils = getAllUtils(); return { contents: [ { uri, mimeType: 'application/json', text: JSON.stringify(utils, null, 2), }, ], }; } if (uri === 'genesis://types') { const types = getAllTypes(); return { contents: [ { uri, mimeType: 'application/json', text: JSON.stringify(types, null, 2), }, ], }; } if (uri === 'genesis://tokens') { const tokens = getAllDesignTokens(); return { contents: [ { uri, mimeType: 'application/json', text: JSON.stringify(tokens, null, 2), }, ], }; } if (uri.startsWith('genesis://component/')) { const componentName = uri.replace('genesis://component/', ''); const details = getComponentDetails(componentName); if (!details) { throw new Error(`Component ${componentName} not found`); } let markdown = `# ${componentName}\n\n`; markdown += `**Path:** \`${details.path}\`\n\n`; markdown += `**Files:** ${details.allFiles.join(', ')}\n\n`; // Extract and display exported types prominently const exportedTypes = extractExportedTypes(details.files.main?.content); if (exportedTypes.length > 0) { markdown += `## 🎯 Exported Types (REUSE THESE!)\n\n`; markdown += `This component exports ${exportedTypes.length} reusable type(s)/constant(s):\n\n`; for (const type of exportedTypes) { markdown += `### ${type.name}\n`; markdown += `- **Kind:** ${type.kind}\n`; markdown += `- **Definition:** \`${type.definition}\`\n`; markdown += `- **Import:** \`import type { ${type.name} } from 'src/components/${componentName}';\`\n\n`; } markdown += `⚠️ **IMPORTANT:** Always import and reuse these types instead of redefining them!\n\n`; } if (details.files.story) { markdown += `## Story Documentation\n\n`; markdown += '```typescript\n'; markdown += details.files.story.content; markdown += '\n```\n\n'; } if (details.files.main) { markdown += `## Component Implementation\n\n`; markdown += '```typescript\n'; markdown += details.files.main.content; markdown += '\n```\n\n'; } if (details.files.css) { markdown += `## Styles\n\n`; markdown += '```css\n'; markdown += details.files.css.content; markdown += '\n```\n\n'; } // Display additional files (sub-components) if (details.files.additional && Object.keys(details.files.additional).length > 0) { markdown += `## Additional Files (Sub-components)\n\n`; for (const [fileName, fileData] of Object.entries(details.files.additional)) { markdown += `### ${fileName}\n\n`; markdown += '```typescript\n'; markdown += fileData.content; markdown += '\n```\n\n'; } } return { contents: [ { uri, mimeType: 'text/markdown', text: markdown, }, ], }; } if (uri.startsWith('genesis://hook/')) { const hookName = uri.replace('genesis://hook/', ''); const details = getHookDetails(hookName); if (!details) { throw new Error(`Hook ${hookName} not found`); } let markdown = `# ${hookName}\n\n`; markdown += `**Path:** \`${details.path}\`\n\n`; markdown += `**Files:** ${details.allFiles.join(', ')}\n\n`; if (details.files.story) { markdown += `## Story Documentation\n\n`; markdown += '```typescript\n'; markdown += details.files.story.content; markdown += '\n```\n\n'; } if (details.files.main) { markdown += `## Hook Implementation\n\n`; markdown += '```typescript\n'; markdown += details.files.main.content; markdown += '\n```\n\n'; } return { contents: [ { uri, mimeType: 'text/markdown', text: markdown, }, ], }; } if (uri.startsWith('genesis://util/')) { const utilName = uri.replace('genesis://util/', ''); const details = getUtilDetails(utilName); if (!details) { throw new Error(`Util ${utilName} not found`); } let markdown = `# ${utilName}\n\n`; markdown += `**Path:** \`${details.path}\`\n\n`; markdown += `**Files:** ${details.allFiles.join(', ')}\n\n`; // Display all files in the util module if (details.files && Object.keys(details.files).length > 0) { markdown += `## Utility Files\n\n`; for (const [fileName, fileData] of Object.entries(details.files)) { markdown += `### ${fileName}\n\n`; markdown += '```typescript\n'; markdown += fileData.content; markdown += '\n```\n\n'; } } return { contents: [ { uri, mimeType: 'text/markdown', text: markdown, }, ], }; } if (uri.startsWith('genesis://type/')) { const typeName = uri.replace('genesis://type/', ''); const details = getTypeDetails(typeName); if (!details) { throw new Error(`Type ${typeName} not found`); } let markdown = `# ${typeName}\n\n`; markdown += `**Path:** \`${details.path}\`\n\n`; markdown += `## Type Definitions\n\n`; markdown += '```typescript\n'; markdown += details.content; markdown += '\n```\n\n'; return { contents: [ { uri, mimeType: 'text/markdown', text: markdown, }, ], }; } throw new Error(`Unknown resource: ${uri}`); }); /** * List available tools */ server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: 'list_components', description: 'List all available components in the design system', inputSchema: { type: 'object', properties: {}, }, }, { name: 'get_component', description: 'Get detailed information about a specific component including its TypeScript definition, story, styles, and EXPORTED TYPES. The exported types section shows reusable types/interfaces/constants that you should import and use instead of redefining them (e.g., IconType, IconSize from Icon component).', inputSchema: { type: 'object', properties: { name: { type: 'string', description: 'Name of the component (e.g., "Button", "Dropdown", "Icon")', }, }, required: ['name'], }, }, { name: 'search_components', description: 'Search for components by name', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Search query', }, }, required: ['query'], }, }, { name: 'list_hooks', description: 'List all available hooks in the design system', inputSchema: { type: 'object', properties: {}, }, }, { name: 'get_hook', description: 'Get detailed information about a specific hook', inputSchema: { type: 'object', properties: { name: { type: 'string', description: 'Name of the hook (e.g., "useDebounce", "useModal")', }, }, required: ['name'], }, }, { name: 'list_utils', description: 'List all available utility modules in the design system', inputSchema: { type: 'object', properties: {}, }, }, { name: 'get_util', description: 'Get detailed information about a specific utility module including all its functions', inputSchema: { type: 'object', properties: { name: { type: 'string', description: 'Name of the utility module (e.g., "classify", "qa", "dom")', }, }, required: ['name'], }, }, { name: 'list_types', description: 'List all available type definitions in the design system', inputSchema: { type: 'object', properties: {}, }, }, { name: 'get_type', description: 'Get detailed information about a specific type definition file', inputSchema: { type: 'object', properties: { name: { type: 'string', description: 'Name of the type file (e.g., "common", "typography", "charts")', }, }, required: ['name'],