Skip to main content
Glama

UI/UX MCP Server

by willem4130
workflows.tsโ€ข12.6 kB
import { z } from 'zod'; import { StorybookTools } from './storybook.js'; import { TailwindTools } from './tailwind.js'; import { AnimationTools } from './animation.js'; import { PlaywrightTools } from './playwright.js'; import { ComponentTools } from './components.js'; // Removed DesignToCode workflow since it depends on Figma const OptimizeUXSchema = z.object({ url: z.string().url(), analyses: z.array(z.enum(['performance', 'accessibility', 'usability', 'mobile', 'seo'])).optional() }); const BuildDesignSystemSchema = z.object({ source: z.enum(['storybook', 'code']), sourceId: z.string(), includeTokens: z.boolean().default(true), includeComponents: z.boolean().default(true), includeDocumentation: z.boolean().default(true) }); export class WorkflowTools { private storybookTools: StorybookTools; private tailwindTools: TailwindTools; private animationTools: AnimationTools; private playwrightTools: PlaywrightTools; private componentTools: ComponentTools; constructor() { this.storybookTools = new StorybookTools(); this.tailwindTools = new TailwindTools(); this.animationTools = new AnimationTools(); this.playwrightTools = new PlaywrightTools(); this.componentTools = new ComponentTools(); } // Removed designToCode method as it depends on Figma API async optimizeUX(args: any) { const params = OptimizeUXSchema.parse(args); const analyses = params.analyses || ['performance', 'accessibility', 'usability']; try { const results: any = { url: params.url, timestamp: new Date().toISOString(), analyses: {}, recommendations: [], overallScore: 0 }; // Performance Analysis if (analyses.includes('performance')) { results.analyses.performance = await this.analyzePerformance(params.url); } // Accessibility Analysis if (analyses.includes('accessibility')) { results.analyses.accessibility = await this.analyzeAccessibility(params.url); } // Usability Analysis if (analyses.includes('usability')) { results.analyses.usability = await this.analyzeUsability(params.url); } // Mobile Analysis if (analyses.includes('mobile')) { results.analyses.mobile = await this.analyzeMobile(params.url); } // SEO Analysis if (analyses.includes('seo')) { results.analyses.seo = await this.analyzeSEO(params.url); } // Calculate overall score const scores = Object.values(results.analyses).map((a: any) => a.score || 0); results.overallScore = scores.reduce((a, b) => a + b, 0) / scores.length; // Generate recommendations results.recommendations = this.generateUXRecommendations(results.analyses); return { content: [ { type: 'text', text: JSON.stringify(results, null, 2) } ] }; } catch (error: any) { return { content: [ { type: 'text', text: `Error optimizing UX: ${error.message}` } ], isError: true }; } } async buildDesignSystem(args: any) { const params = BuildDesignSystemSchema.parse(args); try { const designSystem: any = { name: 'Design System', version: '1.0.0', source: params.source, created: new Date().toISOString(), tokens: {}, components: [], documentation: {} }; switch (params.source) { case 'storybook': // Build from Storybook if (params.includeComponents) { const stories = await this.storybookTools.getStories({ url: params.sourceId }); designSystem.components = this.extractStorybookComponents(stories); } break; case 'code': // Build from code analysis designSystem.components = await this.analyzeCodeComponents(params.sourceId); break; } if (params.includeDocumentation) { designSystem.documentation = this.generateSystemDocumentation(designSystem); } // Generate output files const outputs = { tokens: params.includeTokens ? this.generateTokenFiles(designSystem.tokens) : null, components: params.includeComponents ? this.generateComponentFiles(designSystem.components) : null, documentation: params.includeDocumentation ? this.generateDocFiles(designSystem.documentation) : null }; return { content: [ { type: 'text', text: JSON.stringify({ designSystem, outputs, summary: { tokenCategories: Object.keys(designSystem.tokens).length, componentCount: designSystem.components.length, documentationPages: Object.keys(designSystem.documentation).length } }, null, 2) } ] }; } catch (error: any) { return { content: [ { type: 'text', text: `Error building design system: ${error.message}` } ], isError: true }; } } private getDefaultDesignTokens(): any { // Return default design tokens return { colors: { primary: '#007AFF', secondary: '#5856D6', success: '#34C759', warning: '#FF9500', error: '#FF3B30', neutral: { 100: '#F2F2F7', 200: '#E5E5EA', 300: '#C7C7CC', 400: '#8E8E93', 500: '#636366', 600: '#48484A', 700: '#363638', 800: '#2C2C2E', 900: '#1C1C1E' } }, spacing: { xs: '4px', sm: '8px', md: '16px', lg: '24px', xl: '32px', xxl: '48px' }, typography: { fontFamily: { sans: ['Inter', 'system-ui', 'sans-serif'], mono: ['Fira Code', 'monospace'] }, fontSize: { xs: '12px', sm: '14px', base: '16px', lg: '18px', xl: '20px', '2xl': '24px', '3xl': '30px', '4xl': '36px' } }, borderRadius: { none: '0', sm: '4px', md: '8px', lg: '12px', full: '9999px' } }; } // Removed generateComponentFromDesign as it depends on Figma private generateStyles(styling: string, tokens: any): any { switch (styling) { case 'tailwind': return { classes: 'bg-primary text-white px-4 py-2 rounded-md hover:bg-primary-dark' }; case 'css': return { backgroundColor: tokens.colors.primary, color: 'white', padding: `${tokens.spacing.sm} ${tokens.spacing.md}`, borderRadius: tokens.borderRadius.md }; case 'styled-components': case 'emotion': return { template: ` background-color: \${props => props.theme.colors.primary}; color: white; padding: \${props => props.theme.spacing.sm} \${props => props.theme.spacing.md}; border-radius: \${props => props.theme.borderRadius.md}; ` }; default: return {}; } } private async analyzePerformance(url: string): Promise<any> { // Simulate performance analysis return { score: 85, metrics: { firstContentfulPaint: '1.2s', largestContentfulPaint: '2.4s', totalBlockingTime: '150ms', cumulativeLayoutShift: 0.05, speedIndex: '3.2s' }, opportunities: [ 'Reduce JavaScript bundle size', 'Optimize images', 'Enable text compression' ] }; } private async analyzeAccessibility(url: string): Promise<any> { // Simulate accessibility analysis return { score: 92, violations: [ { rule: 'color-contrast', impact: 'serious', elements: 3 } ], passes: [ 'aria-roles', 'button-name', 'image-alt', 'label' ] }; } private async analyzeUsability(url: string): Promise<any> { // Simulate usability analysis return { score: 78, findings: { navigation: 'Clear and intuitive', formUsability: 'Good, but could improve error messages', readability: 'Excellent typography and spacing', interactionFeedback: 'Needs improvement on loading states' } }; } private async analyzeMobile(url: string): Promise<any> { // Simulate mobile analysis return { score: 88, viewport: 'Properly configured', touchTargets: 'Most are appropriately sized', textSize: 'Readable without zooming', responsive: true, issues: [ 'Some buttons too small for touch', 'Horizontal scrolling on smaller devices' ] }; } private async analyzeSEO(url: string): Promise<any> { // Simulate SEO analysis return { score: 75, meta: { title: 'Present', description: 'Present', keywords: 'Missing', ogTags: 'Partial' }, structured: false, sitemap: false, robots: true }; } private generateUXRecommendations(analyses: any): string[] { const recommendations = []; if (analyses.performance?.score < 90) { recommendations.push('Optimize page load performance for better user experience'); } if (analyses.accessibility?.violations?.length > 0) { recommendations.push('Fix accessibility violations to ensure inclusive design'); } if (analyses.mobile?.issues?.length > 0) { recommendations.push('Improve mobile experience with better touch targets'); } if (analyses.seo?.score < 80) { recommendations.push('Enhance SEO with better meta tags and structured data'); } return recommendations; } // Removed extractFigmaComponents as it depends on Figma API private extractStorybookComponents(stories: any): any[] { // Extract components from Storybook stories return [ { name: 'Button', stories: ['Default', 'Primary', 'Secondary'], props: ['variant', 'size', 'disabled'] } ]; } private async analyzeCodeComponents(path: string): Promise<any[]> { // Analyze code to extract components return [ { name: 'Header', file: 'Header.tsx', props: ['title', 'navigation'] }, { name: 'Footer', file: 'Footer.tsx', props: ['links', 'copyright'] } ]; } private generateSystemDocumentation(designSystem: any): any { return { overview: 'Design System Overview', gettingStarted: 'Installation and setup guide', tokens: 'Design tokens documentation', components: 'Component library documentation', patterns: 'Design patterns and best practices', contributing: 'Contributing guidelines' }; } private generateTokenFiles(tokens: any): any { return { 'tokens.json': JSON.stringify(tokens, null, 2), 'tokens.css': this.tokensToCSS(tokens), 'tokens.js': `export default ${JSON.stringify(tokens, null, 2)}` }; } private generateComponentFiles(components: any[]): any { const files: any = {}; components.forEach(comp => { files[`${comp.name}.component.js`] = '// Component implementation'; files[`${comp.name}.stories.js`] = '// Storybook stories'; files[`${comp.name}.test.js`] = '// Component tests'; }); return files; } private generateDocFiles(documentation: any): any { const files: any = {}; Object.entries(documentation).forEach(([key, content]) => { files[`${key}.md`] = content; }); return files; } private tokensToCSS(tokens: any): string { let css = ':root {\n'; // Colors Object.entries(tokens.colors || {}).forEach(([key, value]: [string, any]) => { if (typeof value === 'string') { css += ` --color-${key}: ${value};\n`; } else if (typeof value === 'object') { Object.entries(value).forEach(([subKey, subValue]) => { css += ` --color-${key}-${subKey}: ${subValue};\n`; }); } }); // Spacing Object.entries(tokens.spacing || {}).forEach(([key, value]) => { css += ` --spacing-${key}: ${value};\n`; }); css += '}'; return css; } }

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/willem4130/ui-ux-mcp-server'

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