# Directory Structure
```
├── .env.example
├── .gitignore
├── package-lock.json
├── package.json
├── README.md
├── src
│ ├── cli.ts
│ ├── index.ts
│ ├── server.ts
│ └── services
│ ├── component-parser.ts
│ └── github.ts
└── tsconfig.json
```
# Files
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
1 | # Dependency directories
2 | node_modules/
3 |
4 | # Build output
5 | dist/
6 |
7 | # Environment variables
8 | .env
9 |
10 | # OS specific
11 | .DS_Store
12 | Thumbs.db
13 |
14 | # Cache files
15 | cache/
16 |
17 | # Log files
18 | server.log
19 | *.log
20 |
```
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
```
1 | # GitHub API Token - Create one at https://github.com/settings/tokens
2 | # Required for higher API rate limits when fetching components from GitHub
3 | GITHUB_TOKEN=your_github_token_here
4 |
5 | # Transport type for the MCP server - Options: stdio or http
6 | # Default: stdio
7 | TRANSPORT_TYPE=stdio
8 |
9 | # Port for the HTTP server when TRANSPORT_TYPE is set to http
10 | # Default: 3000
11 | PORT=3000
12 |
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
1 | # MCP Magic UI
2 |
3 | An MCP (Model Context Protocol) server for accessing and exploring Magic UI components from the [magicuidesign/magicui](https://github.com/magicuidesign/magicui) repository.
4 |
5 | ## What is MCP Magic UI?
6 |
7 | MCP Magic UI is a server that implements the Model Context Protocol (MCP) to provide access to Magic UI components. It fetches component data from the Magic UI GitHub repository, categorizes them, and makes them available through an MCP API. This allows AI assistants and other MCP clients to easily discover and use Magic UI components in their applications.
8 |
9 | ## Features
10 |
11 | - **Component Discovery**: Access all Magic UI components through MCP tools
12 | - **Component Categorization**: Components are automatically categorized based on their names and dependencies
13 | - **Caching System**: Local caching of component data to reduce GitHub API calls and work offline
14 | - **Multiple Transport Options**: Support for both stdio and HTTP transport methods
15 | - **Fallback Mechanism**: Mock data is provided when GitHub API is unavailable
16 |
17 | ## Installation
18 |
19 | ```bash
20 | # Clone the repository
21 | git clone https://github.com/idcdev/mcp-magic-ui.git
22 | cd mcp-magic-ui
23 |
24 | # Install dependencies
25 | npm install
26 |
27 | # Build the project
28 | npm run build
29 | ```
30 |
31 | ## Configuration
32 |
33 | To avoid GitHub API rate limits, it's recommended to set up a GitHub personal access token:
34 |
35 | 1. Create a token at https://github.com/settings/tokens
36 | 2. Create a `.env` file in the project root (or copy from `.env.example`)
37 | 3. Add your token to the `.env` file:
38 |
39 | ```
40 | GITHUB_TOKEN=your_github_token_here
41 | ```
42 |
43 | ## Usage
44 |
45 | ### Starting the server
46 |
47 | You can start the server using either stdio or HTTP transport:
48 |
49 | ```bash
50 | # Using stdio transport (default)
51 | npm start
52 |
53 | # Using HTTP transport
54 | TRANSPORT_TYPE=http npm start
55 | ```
56 |
57 | ### Connecting to the server
58 |
59 | You can connect to the server using any MCP client. For example, using the MCP Inspector:
60 |
61 | ```bash
62 | npx @modelcontextprotocol/inspector mcp-magic-ui
63 | ```
64 |
65 | Or, if using HTTP transport:
66 |
67 | ```bash
68 | npx @modelcontextprotocol/inspector http://localhost:3000
69 | ```
70 |
71 | ## Available Tools
72 |
73 | The server provides the following MCP tools:
74 |
75 | - `get_all_components` - Get a list of all available Magic UI components with their metadata
76 | - `get_component_by_path` - Get the source code of a specific component by its file path
77 |
78 | ## Project Structure
79 |
80 | - `src/` - Source code
81 | - `index.ts` - Main entry point for the server
82 | - `cli.ts` - Command-line interface
83 | - `server.ts` - MCP server configuration and tool definitions
84 | - `services/` - Service modules
85 | - `github.ts` - GitHub API interaction and caching
86 | - `component-parser.ts` - Component categorization and processing
87 |
88 | - `cache/` - Local cache for component data
89 | - `dist/` - Compiled JavaScript code
90 |
91 | ## How It Works
92 |
93 | 1. The server fetches component data from the Magic UI GitHub repository
94 | 2. Component data is cached locally to reduce API calls and enable offline usage
95 | 3. Components are categorized based on their names and dependencies
96 | 4. The server exposes MCP tools to access and search for components
97 | 5. Clients can connect to the server using stdio or HTTP transport
98 |
99 | ## Contributing
100 |
101 | Contributions are welcome! Here are some ways you can contribute:
102 |
103 | - Report bugs and suggest features by creating issues
104 | - Improve documentation
105 | - Submit pull requests with bug fixes or new features
106 |
107 | ## License
108 |
109 | MIT
```
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "compilerOptions": {
3 | "target": "ES2022",
4 | "module": "NodeNext",
5 | "moduleResolution": "NodeNext",
6 | "esModuleInterop": true,
7 | "outDir": "./dist",
8 | "rootDir": "./src",
9 | "strict": true,
10 | "declaration": true,
11 | "skipLibCheck": true,
12 | "forceConsistentCasingInFileNames": true
13 | },
14 | "include": ["src/**/*"],
15 | "exclude": ["node_modules", "dist"]
16 | }
```
--------------------------------------------------------------------------------
/src/cli.ts:
--------------------------------------------------------------------------------
```typescript
1 | #!/usr/bin/env node
2 |
3 | import { createServer } from './server.js';
4 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
5 | import dotenv from 'dotenv';
6 | import fs from 'fs';
7 | import path from 'path';
8 |
9 | // Load environment variables from .env file
10 | dotenv.config();
11 |
12 | // Redirect console.log to console.error
13 | const originalConsoleLog = console.log;
14 | console.log = function(...args) {
15 | console.error(...args);
16 | };
17 |
18 | async function main() {
19 | // Create and start server with stdio transport
20 | const server = await createServer();
21 | const transport = new StdioServerTransport();
22 | await server.connect(transport);
23 | }
24 |
25 | main().catch(error => {
26 | console.error('Error starting MCP server:', error);
27 | process.exit(1);
28 | });
```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "name": "mcp-magic-ui",
3 | "version": "1.0.0",
4 | "description": "MCP server for Magic UI components",
5 | "main": "dist/index.js",
6 | "type": "module",
7 | "bin": {
8 | "mcp-magic-ui": "dist/cli.js"
9 | },
10 | "scripts": {
11 | "build": "tsc",
12 | "start": "node dist/index.js",
13 | "dev": "tsc -w & nodemon dist/index.js",
14 | "test": "echo \"Error: no test specified\" && exit 1",
15 | "postbuild": "chmod +x dist/cli.js"
16 | },
17 | "keywords": [
18 | "mcp",
19 | "magic-ui",
20 | "components"
21 | ],
22 | "author": "",
23 | "license": "MIT",
24 | "dependencies": {
25 | "@modelcontextprotocol/sdk": "^1.6.1",
26 | "@octokit/rest": "^19.0.13",
27 | "@types/node-fetch": "^2.6.12",
28 | "dotenv": "^16.4.7",
29 | "express": "^4.18.2",
30 | "node-fetch": "^3.3.2",
31 | "zod": "^3.22.4"
32 | },
33 | "devDependencies": {
34 | "@types/express": "^4.17.21",
35 | "@types/node": "^20.10.5",
36 | "nodemon": "^3.0.2",
37 | "typescript": "^5.3.3"
38 | }
39 | }
40 |
```
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { createServer } from './server.js';
2 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3 | import express from 'express';
4 | import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
5 | import { Request, Response } from 'express';
6 | import dotenv from 'dotenv';
7 | import path from 'path';
8 | import { fileURLToPath } from 'url';
9 |
10 | // Load environment variables from .env file
11 | dotenv.config();
12 |
13 | const __filename = fileURLToPath(import.meta.url);
14 | const __dirname = path.dirname(__filename);
15 |
16 | async function main() {
17 | try {
18 | const server = await createServer();
19 |
20 | // Determine transport method from environment variable or argument
21 | const transportType = process.env.TRANSPORT_TYPE || 'stdio';
22 |
23 | if (transportType === 'stdio') {
24 | // Use stdio transport
25 | const transport = new StdioServerTransport();
26 | await server.connect(transport);
27 | console.log('MCP server started with stdio transport');
28 | } else if (transportType === 'http') {
29 | // Use HTTP with SSE transport
30 | const app = express();
31 | const port = process.env.PORT || 3000;
32 | let transport: SSEServerTransport;
33 |
34 | app.get('/sse', async (req: Request, res: Response) => {
35 | transport = new SSEServerTransport('/messages', res);
36 | await server.connect(transport);
37 | });
38 |
39 | app.post('/messages', async (req: Request, res: Response) => {
40 | if (transport) {
41 | await transport.handlePostMessage(req, res);
42 | } else {
43 | res.status(400).send('No active transport connection');
44 | }
45 | });
46 |
47 | app.listen(port, () => {
48 | console.log(`MCP server started with HTTP transport on port ${port}`);
49 | });
50 | } else {
51 | console.error(`Unknown transport type: ${transportType}`);
52 | process.exit(1);
53 | }
54 | } catch (error) {
55 | console.error('Error starting MCP server:', error);
56 | process.exit(1);
57 | }
58 | }
59 |
60 | main();
```
--------------------------------------------------------------------------------
/src/server.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2 | import { z } from "zod";
3 | import { GitHubService } from "./services/github.js";
4 | import { ComponentParser } from "./services/component-parser.js";
5 |
6 | export async function createServer() {
7 | // Initialize services
8 | const githubService = new GitHubService();
9 | const componentParser = new ComponentParser(githubService);
10 |
11 | // Load all components
12 | console.error("Loading components...");
13 | await componentParser.loadAllComponents();
14 | console.error("Components loaded successfully!");
15 |
16 | // Create MCP server
17 | const server = new McpServer({
18 | name: "Magic UI Components",
19 | version: "1.0.0",
20 | description: "MCP server for accessing Magic UI components",
21 | });
22 |
23 | // Register all components tool
24 | server.tool(
25 | "get_all_components",
26 | {},
27 | async () => {
28 | // Get the raw content of registry.json
29 | const rawData = githubService.getRawRegistryData();
30 |
31 | // If no data, return processed components as fallback
32 | if (!rawData || rawData.length === 0) {
33 | const components = componentParser.getAllComponents();
34 | return {
35 | content: [{
36 | type: "text",
37 | text: JSON.stringify(components, null, 2),
38 | }],
39 | };
40 | }
41 |
42 | // Return the raw content of registry.json
43 | return {
44 | content: [{
45 | type: "text",
46 | text: JSON.stringify(rawData, null, 2),
47 | }],
48 | };
49 | }
50 | );
51 |
52 | // Adicionar ferramenta para obter um componente específico pelo caminho
53 | server.tool(
54 | "get_component_by_path",
55 | {
56 | path: z.string().describe("Path to the component file"),
57 | },
58 | async (params: any) => {
59 | const path = params.path as string;
60 |
61 | try {
62 | // Obter o conteúdo do arquivo
63 | const content = await githubService.getFileContent(path);
64 |
65 | if (!content) {
66 | return {
67 | content: [{
68 | type: "text",
69 | text: `Component file at path '${path}' not found or empty`,
70 | }],
71 | isError: true,
72 | };
73 | }
74 |
75 | return {
76 | content: [{
77 | type: "text",
78 | text: content,
79 | }],
80 | };
81 | } catch (error) {
82 | return {
83 | content: [{
84 | type: "text",
85 | text: `Error fetching component at path '${path}': ${error}`,
86 | }],
87 | isError: true,
88 | };
89 | }
90 | }
91 | );
92 |
93 | return server;
94 | }
```
--------------------------------------------------------------------------------
/src/services/component-parser.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { GitHubService, RegistryComponent } from './github.js';
2 |
3 | // Simplified interface for components
4 | export interface Component {
5 | name: string;
6 | description: string;
7 | code: string;
8 | category: string;
9 | dependencies?: string[];
10 | files?: {
11 | path: string;
12 | type: string;
13 | target: string;
14 | }[];
15 | title?: string;
16 | }
17 |
18 | export class ComponentParser {
19 | private githubService: GitHubService;
20 | private components: Map<string, Component> = new Map();
21 |
22 | constructor(githubService: GitHubService) {
23 | this.githubService = githubService;
24 | }
25 |
26 | // Load all components
27 | async loadAllComponents(): Promise<void> {
28 | // Load components from registry
29 | await this.githubService.loadRegistryComponents();
30 |
31 | // Convert registry components to Component format
32 | const registryComponents = this.githubService.getAllRegistryComponents();
33 |
34 | for (const registryComponent of registryComponents) {
35 | const component: Component = {
36 | name: registryComponent.name,
37 | description: registryComponent.description,
38 | code: '', // We don't need to load the code here
39 | category: this.determineCategory(registryComponent),
40 | dependencies: registryComponent.dependencies,
41 | files: registryComponent.files,
42 | title: registryComponent.title
43 | };
44 |
45 | this.components.set(component.name, component);
46 | }
47 |
48 | console.error(`Loaded ${this.components.size} components`);
49 | }
50 |
51 | // Determine category based on registry component
52 | private determineCategory(registryComponent: RegistryComponent): string {
53 | const name = registryComponent.name.toLowerCase();
54 |
55 | // If the component has dependencies, use them to help determine the category
56 | if (registryComponent.dependencies) {
57 | if (registryComponent.dependencies.includes('motion')) return 'Animation';
58 | }
59 |
60 | // Determine category based on component name
61 | if (name.includes('button')) return 'Button';
62 | if (name.includes('card')) return 'Card';
63 | if (name.includes('text')) return 'Typography';
64 | if (name.includes('input')) return 'Form';
65 | if (name.includes('form')) return 'Form';
66 | if (name.includes('dialog')) return 'Dialog';
67 | if (name.includes('modal')) return 'Dialog';
68 | if (name.includes('menu')) return 'Navigation';
69 | if (name.includes('nav')) return 'Navigation';
70 | if (name.includes('table')) return 'Data Display';
71 | if (name.includes('list')) return 'Data Display';
72 | if (name.includes('grid')) return 'Layout';
73 | if (name.includes('layout')) return 'Layout';
74 | if (name.includes('animation')) return 'Animation';
75 | if (name.includes('effect')) return 'Effect';
76 |
77 | // Default category
78 | return 'Other';
79 | }
80 |
81 | // Get component by name
82 | getComponent(name: string): Component | undefined {
83 | return this.components.get(name);
84 | }
85 |
86 | // Get all components
87 | getAllComponents(): Component[] {
88 | return Array.from(this.components.values());
89 | }
90 | }
```
--------------------------------------------------------------------------------
/src/services/github.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { Octokit } from '@octokit/rest';
2 | import fs from 'fs';
3 | import path from 'path';
4 | import { fileURLToPath } from 'url';
5 |
6 | // Get the current directory
7 | const __filename = fileURLToPath(import.meta.url);
8 | const __dirname = path.dirname(__filename);
9 |
10 | // Path to cache file
11 | const CACHE_DIR = path.join(__dirname, '../../cache');
12 | const REGISTRY_CACHE_FILE = path.join(CACHE_DIR, 'registry.json');
13 | const CACHE_EXPIRY_MS = 24 * 60 * 60 * 1000; // 24 hours
14 |
15 | export interface RegistryComponent {
16 | name: string;
17 | type: string;
18 | title: string;
19 | description: string;
20 | dependencies?: string[];
21 | files: {
22 | path: string;
23 | type: string;
24 | target: string;
25 | }[];
26 | tailwind?: any;
27 | }
28 |
29 | export class GitHubService {
30 | private octokit: Octokit;
31 | private owner = 'magicuidesign';
32 | private repo = 'magicui';
33 | private componentsPath = 'components';
34 | private registryComponents: Map<string, RegistryComponent> = new Map();
35 | private retryDelay = 1000; // ms
36 | private maxRetries = 3;
37 | private rawRegistryData: any[] = []; // Store raw content of registry.json
38 |
39 | constructor(token?: string) {
40 | // Use token from environment if not provided
41 | const authToken = token || process.env.GITHUB_TOKEN;
42 | this.octokit = new Octokit({
43 | auth: authToken,
44 | request: {
45 | retries: this.maxRetries,
46 | retryAfter: this.retryDelay
47 | }
48 | });
49 |
50 | if (!authToken) {
51 | console.warn('GitHub token not provided. API rate limits will be restricted.');
52 | console.warn('Create a token at https://github.com/settings/tokens and set it as GITHUB_TOKEN in .env file.');
53 | }
54 |
55 | // Create cache directory if it doesn't exist
56 | this.ensureCacheDirectory();
57 | }
58 |
59 | // Ensure cache directory exists
60 | private ensureCacheDirectory(): void {
61 | try {
62 | if (!fs.existsSync(CACHE_DIR)) {
63 | fs.mkdirSync(CACHE_DIR, { recursive: true });
64 | console.error(`Cache directory created at ${CACHE_DIR}`);
65 | }
66 | } catch (error) {
67 | console.error('Error creating cache directory:', error);
68 | }
69 | }
70 |
71 | // Check if cache is valid (not expired)
72 | private isCacheValid(): boolean {
73 | try {
74 | if (!fs.existsSync(REGISTRY_CACHE_FILE)) {
75 | return false;
76 | }
77 |
78 | const stats = fs.statSync(REGISTRY_CACHE_FILE);
79 | const cacheAge = Date.now() - stats.mtimeMs;
80 |
81 | return cacheAge < CACHE_EXPIRY_MS;
82 | } catch (error) {
83 | console.error('Error checking cache validity:', error);
84 | return false;
85 | }
86 | }
87 |
88 | // Save data to cache
89 | private saveToCache(data: string): void {
90 | try {
91 | fs.writeFileSync(REGISTRY_CACHE_FILE, data);
92 | console.error(`Registry data cached to ${REGISTRY_CACHE_FILE}`);
93 | } catch (error) {
94 | console.error('Error saving to cache:', error);
95 | }
96 | }
97 |
98 | // Load data from cache
99 | private loadFromCache(): string | null {
100 | try {
101 | if (this.isCacheValid()) {
102 | console.error('Loading registry from cache');
103 | return fs.readFileSync(REGISTRY_CACHE_FILE, 'utf-8');
104 | }
105 | return null;
106 | } catch (error) {
107 | console.error('Error loading from cache:', error);
108 | return null;
109 | }
110 | }
111 |
112 | async getComponentsList(): Promise<string[]> {
113 | try {
114 | // Fetch list of components from GitHub
115 | const { data } = await this.octokit.repos.getContent({
116 | owner: this.owner,
117 | repo: this.repo,
118 | path: this.componentsPath,
119 | });
120 |
121 | // Filter directories (each directory is a component)
122 | return Array.isArray(data)
123 | ? data
124 | .filter((item: any) => item.type === 'dir')
125 | .map((item: any) => item.name)
126 | : [];
127 | } catch (error) {
128 | console.error('Error fetching components list:', error);
129 | return [];
130 | }
131 | }
132 |
133 | async getComponentFiles(componentName: string): Promise<any[]> {
134 | try {
135 | // Fetch all files for a specific component
136 | const { data } = await this.octokit.repos.getContent({
137 | owner: this.owner,
138 | repo: this.repo,
139 | path: `${this.componentsPath}/${componentName}`,
140 | });
141 |
142 | return Array.isArray(data) ? data : [];
143 | } catch (error) {
144 | console.error(`Error fetching files for component ${componentName}:`, error);
145 | return [];
146 | }
147 | }
148 |
149 | async getFileContent(path: string): Promise<string> {
150 | try {
151 | // Fetch content of a specific file
152 | const { data } = await this.octokit.repos.getContent({
153 | owner: this.owner,
154 | repo: this.repo,
155 | path,
156 | });
157 |
158 | if ('content' in data) {
159 | // Decode content from base64
160 | return Buffer.from(data.content, 'base64').toString('utf-8');
161 | }
162 |
163 | throw new Error(`Could not get content for path: ${path}`);
164 | } catch (error: any) {
165 | // Check if it's a rate limit error
166 | if (error.status === 403 && error.message.includes('API rate limit exceeded')) {
167 | console.error('GitHub API rate limit exceeded. Consider using a token.');
168 | // Return empty string instead of throwing
169 | return '';
170 | }
171 |
172 | console.error(`Error fetching file content for ${path}:`, error);
173 | return '';
174 | }
175 | }
176 |
177 | // Try to load from cache first
178 | async loadRegistryComponents(): Promise<void> {
179 | // Try to load from cache first
180 | let content = this.loadFromCache();
181 |
182 | // If no valid cache, fetch from GitHub
183 | if (!content) {
184 | try {
185 | const response = await this.octokit.repos.getContent({
186 | owner: this.owner,
187 | repo: this.repo,
188 | path: 'registry.json',
189 | });
190 |
191 | // Save to cache if successfully retrieved
192 | if (response.data && 'content' in response.data) {
193 | content = Buffer.from(response.data.content, 'base64').toString('utf-8');
194 | this.saveToCache(content);
195 | }
196 | } catch (error) {
197 | console.error('Error fetching registry from GitHub:', error);
198 | }
199 | }
200 |
201 | // If still no content, use mock data
202 | if (!content) {
203 | console.warn('Using mock data as fallback');
204 | this.loadMockRegistryData();
205 | return;
206 | }
207 |
208 | try {
209 | const data = JSON.parse(content);
210 |
211 | // Check file structure - registry.json has a structure with { name, homepage, items: [] }
212 | if (data && data.items && Array.isArray(data.items)) {
213 | // Store raw content of registry.json (the items)
214 | this.rawRegistryData = data.items;
215 |
216 | // Process each component in the registry
217 | for (const component of data.items) {
218 | if (component.name && component.type) {
219 | this.registryComponents.set(component.name, component as RegistryComponent);
220 | }
221 | }
222 | console.log(`Loaded ${this.registryComponents.size} components`);
223 | } else {
224 | // Old or unexpected structure
225 | console.error('Unexpected registry.json structure');
226 | }
227 | } catch (error) {
228 | console.error('Error parsing registry data:', error);
229 | }
230 | }
231 |
232 | // Get raw registry.json content
233 | getRawRegistryData(): any[] {
234 | return this.rawRegistryData;
235 | }
236 |
237 | getRegistryComponent(name: string): RegistryComponent | undefined {
238 | return this.registryComponents.get(name);
239 | }
240 |
241 | getAllRegistryComponents(): RegistryComponent[] {
242 | return Array.from(this.registryComponents.values());
243 | }
244 |
245 | // Load some mock data for testing when GitHub API fails
246 | private loadMockRegistryData() {
247 | const mockComponents: RegistryComponent[] = [
248 | {
249 | name: 'animated-beam',
250 | type: 'registry:ui',
251 | title: 'Animated Beam',
252 | description: 'An animated beam of light which travels along a path. Useful for showcasing the "integration" features of a website.',
253 | dependencies: ['motion'],
254 | files: [
255 | {
256 | path: 'registry/magicui/animated-beam.tsx',
257 | type: 'registry:ui',
258 | target: 'components/magicui/animated-beam.tsx'
259 | }
260 | ]
261 | },
262 | {
263 | name: 'border-beam',
264 | type: 'registry:ui',
265 | title: 'Border Beam',
266 | description: 'An animated beam of light which travels along the border of its container.',
267 | files: [
268 | {
269 | path: 'registry/magicui/border-beam.tsx',
270 | type: 'registry:ui',
271 | target: 'components/magicui/border-beam.tsx'
272 | }
273 | ]
274 | },
275 | {
276 | name: 'shimmer-button',
277 | type: 'registry:ui',
278 | title: 'Shimmer Button',
279 | description: 'A button with a shimmer effect that moves across the button.',
280 | files: [
281 | {
282 | path: 'registry/magicui/shimmer-button.tsx',
283 | type: 'registry:ui',
284 | target: 'components/magicui/shimmer-button.tsx'
285 | }
286 | ]
287 | }
288 | ];
289 |
290 | mockComponents.forEach(component => {
291 | this.registryComponents.set(component.name, component);
292 | });
293 |
294 | console.error(`Loaded ${this.registryComponents.size} mock components`);
295 | }
296 | }
```