Skip to main content
Glama

MCP Magic UI

by idcdev
github.ts8.9 kB
import { Octokit } from '@octokit/rest'; import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; // Get the current directory const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // Path to cache file const CACHE_DIR = path.join(__dirname, '../../cache'); const REGISTRY_CACHE_FILE = path.join(CACHE_DIR, 'registry.json'); const CACHE_EXPIRY_MS = 24 * 60 * 60 * 1000; // 24 hours export interface RegistryComponent { name: string; type: string; title: string; description: string; dependencies?: string[]; files: { path: string; type: string; target: string; }[]; tailwind?: any; } export class GitHubService { private octokit: Octokit; private owner = 'magicuidesign'; private repo = 'magicui'; private componentsPath = 'components'; private registryComponents: Map<string, RegistryComponent> = new Map(); private retryDelay = 1000; // ms private maxRetries = 3; private rawRegistryData: any[] = []; // Store raw content of registry.json constructor(token?: string) { // Use token from environment if not provided const authToken = token || process.env.GITHUB_TOKEN; this.octokit = new Octokit({ auth: authToken, request: { retries: this.maxRetries, retryAfter: this.retryDelay } }); if (!authToken) { console.warn('GitHub token not provided. API rate limits will be restricted.'); console.warn('Create a token at https://github.com/settings/tokens and set it as GITHUB_TOKEN in .env file.'); } // Create cache directory if it doesn't exist this.ensureCacheDirectory(); } // Ensure cache directory exists private ensureCacheDirectory(): void { try { if (!fs.existsSync(CACHE_DIR)) { fs.mkdirSync(CACHE_DIR, { recursive: true }); console.error(`Cache directory created at ${CACHE_DIR}`); } } catch (error) { console.error('Error creating cache directory:', error); } } // Check if cache is valid (not expired) private isCacheValid(): boolean { try { if (!fs.existsSync(REGISTRY_CACHE_FILE)) { return false; } const stats = fs.statSync(REGISTRY_CACHE_FILE); const cacheAge = Date.now() - stats.mtimeMs; return cacheAge < CACHE_EXPIRY_MS; } catch (error) { console.error('Error checking cache validity:', error); return false; } } // Save data to cache private saveToCache(data: string): void { try { fs.writeFileSync(REGISTRY_CACHE_FILE, data); console.error(`Registry data cached to ${REGISTRY_CACHE_FILE}`); } catch (error) { console.error('Error saving to cache:', error); } } // Load data from cache private loadFromCache(): string | null { try { if (this.isCacheValid()) { console.error('Loading registry from cache'); return fs.readFileSync(REGISTRY_CACHE_FILE, 'utf-8'); } return null; } catch (error) { console.error('Error loading from cache:', error); return null; } } async getComponentsList(): Promise<string[]> { try { // Fetch list of components from GitHub const { data } = await this.octokit.repos.getContent({ owner: this.owner, repo: this.repo, path: this.componentsPath, }); // Filter directories (each directory is a component) return Array.isArray(data) ? data .filter((item: any) => item.type === 'dir') .map((item: any) => item.name) : []; } catch (error) { console.error('Error fetching components list:', error); return []; } } async getComponentFiles(componentName: string): Promise<any[]> { try { // Fetch all files for a specific component const { data } = await this.octokit.repos.getContent({ owner: this.owner, repo: this.repo, path: `${this.componentsPath}/${componentName}`, }); return Array.isArray(data) ? data : []; } catch (error) { console.error(`Error fetching files for component ${componentName}:`, error); return []; } } async getFileContent(path: string): Promise<string> { try { // Fetch content of a specific file const { data } = await this.octokit.repos.getContent({ owner: this.owner, repo: this.repo, path, }); if ('content' in data) { // Decode content from base64 return Buffer.from(data.content, 'base64').toString('utf-8'); } throw new Error(`Could not get content for path: ${path}`); } catch (error: any) { // Check if it's a rate limit error if (error.status === 403 && error.message.includes('API rate limit exceeded')) { console.error('GitHub API rate limit exceeded. Consider using a token.'); // Return empty string instead of throwing return ''; } console.error(`Error fetching file content for ${path}:`, error); return ''; } } // Try to load from cache first async loadRegistryComponents(): Promise<void> { // Try to load from cache first let content = this.loadFromCache(); // If no valid cache, fetch from GitHub if (!content) { try { const response = await this.octokit.repos.getContent({ owner: this.owner, repo: this.repo, path: 'registry.json', }); // Save to cache if successfully retrieved if (response.data && 'content' in response.data) { content = Buffer.from(response.data.content, 'base64').toString('utf-8'); this.saveToCache(content); } } catch (error) { console.error('Error fetching registry from GitHub:', error); } } // If still no content, use mock data if (!content) { console.warn('Using mock data as fallback'); this.loadMockRegistryData(); return; } try { const data = JSON.parse(content); // Check file structure - registry.json has a structure with { name, homepage, items: [] } if (data && data.items && Array.isArray(data.items)) { // Store raw content of registry.json (the items) this.rawRegistryData = data.items; // Process each component in the registry for (const component of data.items) { if (component.name && component.type) { this.registryComponents.set(component.name, component as RegistryComponent); } } console.log(`Loaded ${this.registryComponents.size} components`); } else { // Old or unexpected structure console.error('Unexpected registry.json structure'); } } catch (error) { console.error('Error parsing registry data:', error); } } // Get raw registry.json content getRawRegistryData(): any[] { return this.rawRegistryData; } getRegistryComponent(name: string): RegistryComponent | undefined { return this.registryComponents.get(name); } getAllRegistryComponents(): RegistryComponent[] { return Array.from(this.registryComponents.values()); } // Load some mock data for testing when GitHub API fails private loadMockRegistryData() { const mockComponents: RegistryComponent[] = [ { name: 'animated-beam', type: 'registry:ui', title: 'Animated Beam', description: 'An animated beam of light which travels along a path. Useful for showcasing the "integration" features of a website.', dependencies: ['motion'], files: [ { path: 'registry/magicui/animated-beam.tsx', type: 'registry:ui', target: 'components/magicui/animated-beam.tsx' } ] }, { name: 'border-beam', type: 'registry:ui', title: 'Border Beam', description: 'An animated beam of light which travels along the border of its container.', files: [ { path: 'registry/magicui/border-beam.tsx', type: 'registry:ui', target: 'components/magicui/border-beam.tsx' } ] }, { name: 'shimmer-button', type: 'registry:ui', title: 'Shimmer Button', description: 'A button with a shimmer effect that moves across the button.', files: [ { path: 'registry/magicui/shimmer-button.tsx', type: 'registry:ui', target: 'components/magicui/shimmer-button.tsx' } ] } ]; mockComponents.forEach(component => { this.registryComponents.set(component.name, component); }); console.error(`Loaded ${this.registryComponents.size} mock components`); } }

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/idcdev/mcp-magic-ui'

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