Skip to main content
Glama

KRDS UI/UX MCP Server

by re-rank
krds-loader.ts5.23 kB
import { readFile, readdir, access } from 'fs/promises'; import { join, dirname } from 'path'; import { fileURLToPath } from 'url'; import { cwd } from 'process'; import type { KRDSComponent, DesignToken, TokenSearchResult } from '../types/krds.js'; // 프로젝트 루트 경로 가져오기 function getProjectRoot(): string { // CJS/ESM 불문하고 프로세스 CWD 기준 return cwd(); } /** * KRDS 패키지 루트 경로 찾기 */ function getKRDSPath(): string { // node_modules에서 krds-uiux 찾기 const projectRoot = getProjectRoot(); return join(projectRoot, 'node_modules', 'krds-uiux'); } /** * KRDS 컴포넌트 로더 */ export class KRDSLoader { private krdsPath: string; private componentsCache: KRDSComponent[] | null = null; private tokensCache: any = null; constructor() { this.krdsPath = getKRDSPath(); } /** * 모든 컴포넌트 로드 */ async loadComponents(): Promise<KRDSComponent[]> { if (this.componentsCache) { return this.componentsCache; } const componentsPath = join(this.krdsPath, 'html', 'code'); const components: KRDSComponent[] = []; try { const files = await readdir(componentsPath); for (const file of files) { if (!file.endsWith('.html')) continue; const filePath = join(componentsPath, file); const htmlCode = await readFile(filePath, 'utf-8'); const name = file.replace('.html', ''); const category = this.categorizeComponent(name); components.push({ name, fileName: file, category, htmlCode, description: this.extractDescription(htmlCode) }); } } catch { // 패키지가 누락되었거나 경로가 잘못된 경우 빈 배열 반환 return []; } this.componentsCache = components; return components; } /** * 컴포넌트 카테고리 분류 */ private categorizeComponent(name: string): string { const categories: Record<string, string[]> = { 'Form': ['button', 'checkbox', 'radio', 'select', 'text_input', 'textarea', 'file_upload', 'date_input', 'toggle_switch'], 'Navigation': ['header', 'footer', 'breadcrumb', 'tab', 'pagination', 'side_navigation', 'in_page_navigation', 'main_menu', 'skip_link'], 'Layout': ['accordion', 'carousel', 'modal', 'disclosure', 'structured_list', 'table'], 'Feedback': ['badge', 'tag', 'spinner', 'tooltip', 'critical_alerts', 'step_indicator'], 'Content': ['link', 'text_list', 'calendar'], 'Help': ['coach_mark', 'contextual_help', 'help_panel', 'tutorial_panel'], 'Utility': ['language_switcher', 'masthead', 'identifier', 'resize'] }; for (const [category, keywords] of Object.entries(categories)) { if (keywords.some(keyword => name.includes(keyword))) { return category; } } return 'Other'; } /** * HTML 주석에서 설명 추출 */ private extractDescription(htmlCode: string): string { const match = htmlCode.match(/<!--\s*(.+?)\s*-->/); return match ? match[1] : ''; } /** * 디자인 토큰 로드 */ async loadTokens(): Promise<any> { if (this.tokensCache) { return this.tokensCache; } const tokensPath = join(this.krdsPath, 'tokens', 'figma_token.json'); const tokensData = await readFile(tokensPath, 'utf-8'); this.tokensCache = JSON.parse(tokensData); return this.tokensCache; } /** * 토큰 검색 */ async searchTokens(type?: string, query?: string): Promise<TokenSearchResult[]> { const tokens = await this.loadTokens(); const results: TokenSearchResult[] = []; const traverse = (obj: any, path: string[] = []) => { for (const [key, value] of Object.entries(obj)) { const currentPath = [...path, key]; if (value && typeof value === 'object') { if ('value' in value && 'type' in value) { // 토큰 발견 const tokenType = (value as any).type; const tokenValue = (value as any).value; const pathString = currentPath.join('.'); // 필터링 if (type && tokenType !== type) continue; if (query && !pathString.toLowerCase().includes(query.toLowerCase())) continue; results.push({ name: pathString, value: tokenValue, type: tokenType, cssVariable: `--${currentPath.join('-')}`, scssVariable: `$${currentPath.join('-')}` }); } else { // 계속 탐색 traverse(value, currentPath); } } } }; traverse(tokens); return results; } /** * 리소스 경로 가져오기 */ async getResourcePaths(resourceType: 'css' | 'scss' | 'fonts' | 'images' | 'js'): Promise<string[]> { const resourcesPath = join(this.krdsPath, 'resources'); const targetPath = join(resourcesPath, resourceType); try { const files = await readdir(targetPath); return files.map(file => join('node_modules', 'krds-uiux', 'resources', resourceType, file)); } catch { return []; } } }

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/re-rank/UIUX-MCP'

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