Skip to main content
Glama

MCP Design System Bridge

component.ts5.93 kB
import { ComponentAnalysis, ReactAssets } from '../types/mcp'; import * as fs from 'fs/promises'; import * as path from 'path'; import * as ts from 'typescript'; export class ComponentAnalyzer { async analyze(filePath: string): Promise<ComponentAnalysis> { const sourceFile = await this.readSourceFile(filePath); const props = this.extractProps(sourceFile); const variants = this.extractVariants(sourceFile); const states = this.extractStates(sourceFile); const tokens = this.extractTokens(sourceFile); const dependencies = this.extractDependencies(sourceFile); return { name: path.basename(filePath, '.tsx'), props, variants, states, tokens, dependencies, }; } async getLocalAssets(): Promise<ReactAssets> { const componentsDir = path.join(process.cwd(), 'src', 'components'); const components = await this.findComponents(componentsDir); return { components: await Promise.all(components.map(c => this.analyze(c))), }; } private async readSourceFile(filePath: string): Promise<ts.SourceFile> { const content = await fs.readFile(filePath, 'utf-8'); return ts.createSourceFile( filePath, content, ts.ScriptTarget.Latest, true ); } private extractProps(sourceFile: ts.SourceFile): any[] { const props: any[] = []; const visit = (node: ts.Node) => { if (ts.isInterfaceDeclaration(node) && node.name.text.endsWith('Props')) { for (const member of node.members) { if (ts.isPropertySignature(member)) { props.push({ name: member.name.getText(sourceFile), type: member.type?.getText(sourceFile) || 'any', required: !member.questionToken, description: this.extractJSDoc(member), }); } } } ts.forEachChild(node, visit); }; visit(sourceFile); return props; } private extractVariants(sourceFile: ts.SourceFile): any[] { const variants: any[] = []; const visit = (node: ts.Node) => { if (ts.isCallExpression(node) && node.expression.getText(sourceFile) === 'cva') { const variantsArg = node.arguments[0]; if (ts.isObjectLiteralExpression(variantsArg)) { for (const prop of variantsArg.properties) { if (ts.isPropertyAssignment(prop) && prop.name.getText(sourceFile) === 'variants') { const variantsObj = prop.initializer; if (ts.isObjectLiteralExpression(variantsObj)) { for (const variant of variantsObj.properties) { if (ts.isPropertyAssignment(variant)) { variants.push({ name: variant.name.getText(sourceFile), props: this.parseVariantProps(variant.initializer), }); } } } } } } } ts.forEachChild(node, visit); }; visit(sourceFile); return variants; } private extractStates(sourceFile: ts.SourceFile): any[] { const states: any[] = []; const visit = (node: ts.Node) => { if (ts.isCallExpression(node) && node.expression.getText(sourceFile).includes('useState')) { const stateName = node.parent.getText(sourceFile).split('=')[0].trim(); states.push({ name: stateName, props: { value: node.arguments[0]?.getText(sourceFile), }, }); } ts.forEachChild(node, visit); }; visit(sourceFile); return states; } private extractTokens(sourceFile: ts.SourceFile): string[] { const tokens: string[] = []; const visit = (node: ts.Node) => { if (ts.isStringLiteral(node) && node.text.startsWith('var(--')) { tokens.push(node.text); } ts.forEachChild(node, visit); }; visit(sourceFile); return tokens; } private extractDependencies(sourceFile: ts.SourceFile): string[] { const dependencies: string[] = []; const visit = (node: ts.Node) => { if (ts.isImportDeclaration(node)) { const moduleSpecifier = node.moduleSpecifier.getText(sourceFile); if (moduleSpecifier.startsWith('"') || moduleSpecifier.startsWith("'")) { dependencies.push(moduleSpecifier.slice(1, -1)); } } ts.forEachChild(node, visit); }; visit(sourceFile); return dependencies; } private async findComponents(dir: string): Promise<string[]> { const components: string[] = []; const entries = await fs.readdir(dir, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(dir, entry.name); if (entry.isDirectory()) { components.push(...await this.findComponents(fullPath)); } else if (entry.isFile() && entry.name.endsWith('.tsx')) { components.push(fullPath); } } return components; } private extractJSDoc(node: ts.Node): string | undefined { const jsDoc = ts.getJSDocTags(node); return jsDoc.map(tag => tag.comment).join(' '); } private parseVariantProps(node: ts.Expression): Record<string, any> { if (ts.isObjectLiteralExpression(node)) { const props: Record<string, any> = {}; for (const prop of node.properties) { if (ts.isPropertyAssignment(prop)) { props[prop.name.getText()] = this.parseValue(prop.initializer); } } return props; } return {}; } private parseValue(node: ts.Expression): any { if (ts.isStringLiteral(node)) { return node.text; } if (ts.isNumericLiteral(node)) { return node.text; } if (ts.isObjectLiteralExpression(node)) { return this.parseVariantProps(node); } if (ts.isArrayLiteralExpression(node)) { return node.elements.map(e => this.parseValue(e)); } return undefined; } }

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/brunonepomuceno/mcp-design-system-bridge-cursor'

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