#
tokens: 7357/50000 5/5 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .gitignore
├── LICENSE
├── package-lock.json
├── package.json
├── README.md
├── src
│   └── index.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
1 | node_modules/
2 | build/
3 | *.log
4 | .env*
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | # shadcn-ui MCP Server
  2 | 
  3 | MCP server for shadcn/ui component references
  4 | 
  5 | This is a TypeScript-based MCP server that provides reference information for shadcn/ui components. It implements a Model Context Protocol (MCP) server that helps AI assistants access shadcn/ui component documentation and examples.
  6 | 
  7 | ## Features
  8 | 
  9 | ### Tools
 10 | 
 11 | - `list_shadcn_components` - Get a list of all available shadcn/ui components
 12 | - `get_component_details` - Get detailed information about a specific component
 13 | - `get_component_examples` - Get usage examples for a specific component
 14 | - `search_components` - Search for components by keyword
 15 | 
 16 | ### Functionality
 17 | 
 18 | This server scrapes and caches information from:
 19 | - The official shadcn/ui documentation site (https://ui.shadcn.com)
 20 | - The shadcn/ui GitHub repository
 21 | 
 22 | It provides structured data including:
 23 | - Component descriptions
 24 | - Installation instructions
 25 | - Usage examples
 26 | - Props and variants
 27 | - Code samples
 28 | 
 29 | ## Development
 30 | 
 31 | Install dependencies:
 32 | ```bash
 33 | npm install
 34 | ```
 35 | 
 36 | Build the server:
 37 | ```bash
 38 | npm run build
 39 | ```
 40 | 
 41 | For development with auto-rebuild:
 42 | ```bash
 43 | npm run watch
 44 | ```
 45 | 
 46 | ## Installation
 47 | 
 48 | ### Claude Desktop Configuration
 49 | 
 50 | To use with Claude Desktop, add the server config:
 51 | 
 52 | On MacOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
 53 | On Windows: `%APPDATA%/Claude/claude_desktop_config.json`
 54 | 
 55 | #### Option 1: Using local build
 56 | 
 57 | ```json
 58 | {
 59 |   "mcpServers": {
 60 |     "shadcn-ui-server": {
 61 |       "command": "/path/to/shadcn-ui-server/build/index.js"
 62 |     }
 63 |   }
 64 | }
 65 | ```
 66 | 
 67 | #### Option 2: Using npx command
 68 | 
 69 | ```json
 70 | {
 71 |   "mcpServers": {
 72 |     "shadcn-ui-server": {
 73 |       "command": "npx",
 74 |       "args": ["-y", "shadcn-ui-mcp-server"]
 75 |     }
 76 |   }
 77 | }
 78 | ```
 79 | 
 80 | ### Windsurf Configuration
 81 | 
 82 | Add this to your `./codeium/windsurf/model_config.json`:
 83 | 
 84 | ```json
 85 | {
 86 |   "mcpServers": {
 87 |     "shadcn-ui-server": {
 88 |       "command": "npx",
 89 |       "args": ["-y", "shadcn-ui-mcp-server"]
 90 |     }
 91 |   }
 92 | }
 93 | ```
 94 | 
 95 | ### Cursor Configuration
 96 | 
 97 | Add this to your `.cursor/mcp.json`:
 98 | 
 99 | ```json
100 | {
101 |   "mcpServers": {
102 |     "shadcn-ui-server": {
103 |       "command": "npx",
104 |       "args": ["-y", "shadcn-ui-mcp-server"]
105 |     }
106 |   }
107 | }
108 | ```
109 | 
110 | ### Debugging
111 | 
112 | Since MCP servers communicate over stdio, debugging can be challenging. We recommend using the [MCP Inspector](https://github.com/modelcontextprotocol/inspector), which is available as a package script:
113 | 
114 | ```bash
115 | npm run inspector
116 | ```
117 | 
118 | The Inspector will provide a URL to access debugging tools in your browser.
119 | 
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "ES2022",
 4 |     "module": "Node16",
 5 |     "moduleResolution": "Node16",
 6 |     "outDir": "./build",
 7 |     "rootDir": "./src",
 8 |     "strict": true,
 9 |     "esModuleInterop": true,
10 |     "skipLibCheck": true,
11 |     "forceConsistentCasingInFileNames": true
12 |   },
13 |   "include": ["src/**/*"],
14 |   "exclude": ["node_modules"]
15 | }
16 | 
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "shadcn-ui-mcp-server",
 3 |   "version": "0.1.2",
 4 |   "description": "MCP server for shadcn/ui component references",
 5 |   "type": "module",
 6 |   "license": "MIT",
 7 |   "bin": {
 8 |     "shadcn-ui-server": "build/index.js"
 9 |   },
10 |   "files": [
11 |     "build"
12 |   ],
13 |   "scripts": {
14 |     "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
15 |     "prepare": "npm run build",
16 |     "watch": "tsc --watch",
17 |     "inspector": "npx @modelcontextprotocol/inspector build/index.js"
18 |   },
19 |   "dependencies": {
20 |     "@modelcontextprotocol/sdk": "0.6.0",
21 |     "axios": "^1.7.9",
22 |     "cheerio": "^1.0.0-rc.12"
23 |   },
24 |   "devDependencies": {
25 |     "@types/node": "^20.11.24",
26 |     "typescript": "^5.3.3"
27 |   },
28 |   "repository": {
29 |     "type": "git",
30 |     "url": "git+https://github.com/ymadd/shadcn-ui-mcp-server.git"
31 |   },
32 |   "author": "ymadd",
33 |   "bugs": {
34 |     "url": "https://github.com/ymadd/shadcn-ui-mcp-server/issues"
35 |   },
36 |   "homepage": "https://github.com/ymadd/shadcn-ui-mcp-server#readme",
37 |   "keywords": [
38 |     "shadcn/ui",
39 |     "MCP",
40 |     "model context protocol",
41 |     "component references"
42 |   ]
43 | }
44 | 
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
  1 | #!/usr/bin/env node
  2 | 
  3 | /**
  4 |  * MCP server for shadcn/ui component references
  5 |  * This server provides tools to:
  6 |  * - List all available shadcn/ui components
  7 |  * - Get detailed information about specific components
  8 |  * - Get usage examples for components
  9 |  * - Search for components by keyword
 10 |  */
 11 | 
 12 | import { Server } from "@modelcontextprotocol/sdk/server/index.js";
 13 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
 14 | import {
 15 |   CallToolRequestSchema,
 16 |   ErrorCode,
 17 |   ListToolsRequestSchema,
 18 |   McpError,
 19 | } from "@modelcontextprotocol/sdk/types.js";
 20 | import axios from "axios";
 21 | import * as cheerio from "cheerio";
 22 | 
 23 | /**
 24 |  * Interface for component information
 25 |  */
 26 | interface ComponentInfo {
 27 |   name: string;
 28 |   description: string;
 29 |   url: string;
 30 |   sourceUrl?: string;
 31 |   apiReference?: string;
 32 |   installation?: string;
 33 |   usage?: string;
 34 |   props?: Record<string, ComponentProp>;
 35 |   examples?: ComponentExample[];
 36 | }
 37 | 
 38 | /**
 39 |  * Interface for component property information
 40 |  */
 41 | interface ComponentProp {
 42 |   type: string;
 43 |   description: string;
 44 |   required: boolean;
 45 |   default?: string;
 46 |   example?: string;
 47 | }
 48 | 
 49 | /**
 50 |  * Interface for component example
 51 |  */
 52 | interface ComponentExample {
 53 |   title: string;
 54 |   code: string;
 55 |   description?: string;
 56 | }
 57 | 
 58 | /**
 59 |  * ShadcnUiServer class that handles all the component reference functionality
 60 |  */
 61 | class ShadcnUiServer {
 62 |   private server: Server;
 63 |   private axiosInstance;
 64 |   private componentCache: Map<string, ComponentInfo> = new Map();
 65 |   private componentsListCache: ComponentInfo[] | null = null;
 66 |   private readonly SHADCN_DOCS_URL = "https://ui.shadcn.com";
 67 |   private readonly SHADCN_GITHUB_URL = "https://github.com/shadcn-ui/ui";
 68 |   private readonly SHADCN_RAW_GITHUB_URL = "https://raw.githubusercontent.com/shadcn-ui/ui/main";
 69 | 
 70 |   constructor() {
 71 |     this.server = new Server(
 72 |       {
 73 |         name: "shadcn-ui-server",
 74 |         version: "0.1.0",
 75 |       },
 76 |       {
 77 |         capabilities: {
 78 |           tools: {},
 79 |         },
 80 |       }
 81 |     );
 82 | 
 83 |     this.axiosInstance = axios.create({
 84 |       timeout: 10000,
 85 |       headers: {
 86 |         "User-Agent": "Mozilla/5.0 (compatible; ShadcnUiMcpServer/0.1.0)",
 87 |       },
 88 |     });
 89 |     
 90 |     this.setupToolHandlers();
 91 |     
 92 |     this.server.onerror = (error) => console.error("[MCP Error]", error);
 93 |     process.on("SIGINT", async () => {
 94 |       await this.server.close();
 95 |       process.exit(0);
 96 |     });
 97 |   }
 98 | 
 99 |   /**
100 |    * Set up the tool handlers for the server
101 |    */
102 |   private setupToolHandlers() {
103 |     this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
104 |       tools: [
105 |         {
106 |           name: "list_shadcn_components",
107 |           description: "Get a list of all available shadcn/ui components",
108 |           inputSchema: {
109 |             type: "object",
110 |             properties: {},
111 |             required: [],
112 |           },
113 |         },
114 |         {
115 |           name: "get_component_details",
116 |           description: "Get detailed information about a specific shadcn/ui component",
117 |           inputSchema: {
118 |             type: "object",
119 |             properties: {
120 |               componentName: {
121 |                 type: "string",
122 |                 description: "Name of the shadcn/ui component (e.g., \"accordion\", \"button\")",
123 |               },
124 |             },
125 |             required: ["componentName"],
126 |           },
127 |         },
128 |         {
129 |           name: "get_component_examples",
130 |           description: "Get usage examples for a specific shadcn/ui component",
131 |           inputSchema: {
132 |             type: "object",
133 |             properties: {
134 |               componentName: {
135 |                 type: "string",
136 |                 description: "Name of the shadcn/ui component (e.g., \"accordion\", \"button\")",
137 |               },
138 |             },
139 |             required: ["componentName"],
140 |           },
141 |         },
142 |         {
143 |           name: "search_components",
144 |           description: "Search for shadcn/ui components by keyword",
145 |           inputSchema: {
146 |             type: "object",
147 |             properties: {
148 |               query: {
149 |                 type: "string",
150 |                 description: "Search query to find relevant components",
151 |               },
152 |             },
153 |             required: ["query"],
154 |           },
155 |         },
156 |       ],
157 |     }));
158 | 
159 |     this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
160 |       switch (request.params.name) {
161 |         case "list_shadcn_components":
162 |           return await this.handleListComponents();
163 |         case "get_component_details":
164 |           return await this.handleGetComponentDetails(request.params.arguments);
165 |         case "get_component_examples":
166 |           return await this.handleGetComponentExamples(request.params.arguments);
167 |         case "search_components":
168 |           return await this.handleSearchComponents(request.params.arguments);
169 |         default:
170 |           throw new McpError(
171 |             ErrorCode.MethodNotFound,
172 |             `Unknown tool: ${request.params.name}`
173 |           );
174 |       }
175 |     });
176 |   }
177 | 
178 |   /**
179 |    * Handle the list_shadcn_components tool request
180 |    */
181 |   private async handleListComponents() {
182 |     try {
183 |       if (!this.componentsListCache) {
184 |         // Fetch the list of components
185 |         const response = await this.axiosInstance.get(`${this.SHADCN_DOCS_URL}/docs/components`);
186 |         const $ = cheerio.load(response.data);
187 |         
188 |         const components: ComponentInfo[] = [];
189 |         
190 |         // Extract component links
191 |         $("a").each((_, element) => {
192 |           const link = $(element);
193 |           const url = link.attr("href");
194 |           
195 |           if (url && url.startsWith("/docs/components/")) {
196 |             const name = url.split("/").pop() || "";
197 |             
198 |             components.push({
199 |               name,
200 |               description: "", // Will be populated when fetching details
201 |               url: `${this.SHADCN_DOCS_URL}${url}`,
202 |             });
203 |           }
204 |         });
205 |         
206 |         this.componentsListCache = components;
207 |       }
208 |       
209 |       return {
210 |         content: [
211 |           {
212 |             type: "text",
213 |             text: JSON.stringify(this.componentsListCache, null, 2),
214 |           },
215 |         ],
216 |       };
217 |     } catch (error) {
218 |       if (axios.isAxiosError(error)) {
219 |         throw new McpError(
220 |           ErrorCode.InternalError,
221 |           `Failed to fetch shadcn/ui components: ${error.message}`
222 |         );
223 |       }
224 |       throw error;
225 |     }
226 |   }
227 | 
228 |   /**
229 |    * Validates component name from arguments
230 |    * @param args Arguments object
231 |    * @returns Validated component name
232 |    * @throws McpError if validation fails
233 |    */
234 |   private validateComponentName(args: any): string {
235 |     if (!args?.componentName || typeof args.componentName !== "string") {
236 |       throw new McpError(
237 |         ErrorCode.InvalidParams,
238 |         "Component name is required and must be a string"
239 |       );
240 |     }
241 |     return args.componentName.toLowerCase();
242 |   }
243 | 
244 |   /**
245 |    * Validates search query from arguments
246 |    * @param args Arguments object
247 |    * @returns Validated search query
248 |    * @throws McpError if validation fails
249 |    */
250 |   private validateSearchQuery(args: any): string {
251 |     if (!args?.query || typeof args.query !== "string") {
252 |       throw new McpError(
253 |         ErrorCode.InvalidParams,
254 |         "Search query is required and must be a string"
255 |       );
256 |     }
257 |     return args.query.toLowerCase();
258 |   }
259 | 
260 |   /**
261 |    * Handles Axios errors consistently
262 |    * @param error The caught error
263 |    * @param context Context information for the error message
264 |    * @throws McpError with appropriate error code and message
265 |    */
266 |   private handleAxiosError(error: unknown, context: string): never {
267 |     if (axios.isAxiosError(error)) {
268 |       if (error.response?.status === 404) {
269 |         throw new McpError(
270 |           ErrorCode.InvalidParams,
271 |           `${context} not found`
272 |         );
273 |       } else {
274 |         throw new McpError(
275 |           ErrorCode.InternalError,
276 |           `${context}: ${error.message}`
277 |         );
278 |       }
279 |     }
280 |     throw error;
281 |   }
282 | 
283 |   /**
284 |    * Creates a standardized success response
285 |    * @param data Data to include in the response
286 |    * @returns Formatted response object
287 |    */
288 |   private createSuccessResponse(data: any) {
289 |     return {
290 |       content: [
291 |         {
292 |           type: "text",
293 |           text: JSON.stringify(data, null, 2),
294 |         },
295 |       ],
296 |     };
297 |   }
298 | 
299 |   /**
300 |    * Handle the get_component_details tool request
301 |    */
302 |   private async handleGetComponentDetails(args: any) {
303 |     const componentName = this.validateComponentName(args);
304 | 
305 |     try {
306 |       // Check cache first
307 |       if (this.componentCache.has(componentName)) {
308 |         return this.createSuccessResponse(this.componentCache.get(componentName));
309 |       }
310 | 
311 |       // Fetch component details
312 |       const componentInfo = await this.fetchComponentDetails(componentName);
313 |       
314 |       // Save to cache
315 |       this.componentCache.set(componentName, componentInfo);
316 |       
317 |       return this.createSuccessResponse(componentInfo);
318 |     } catch (error) {
319 |       this.handleAxiosError(error, `Component "${componentName}"`);
320 |     }
321 |   }
322 | 
323 |   /**
324 |    * Fetches component details from the shadcn/ui documentation
325 |    * @param componentName Name of the component to fetch
326 |    * @returns Component information
327 |    */
328 |   private async fetchComponentDetails(componentName: string): Promise<ComponentInfo> {
329 |     const response = await this.axiosInstance.get(`${this.SHADCN_DOCS_URL}/docs/components/${componentName}`);
330 |     const $ = cheerio.load(response.data);
331 |     
332 |     // Extract component information
333 |     const title = $("h1").first().text().trim();
334 |     
335 |     // Extract description properly
336 |     const description = this.extractDescription($);
337 |     
338 |     // Extract GitHub source code link
339 |     const sourceUrl = `${this.SHADCN_GITHUB_URL}/tree/main/apps/www/registry/default/ui/${componentName}`;
340 |     
341 |     // Extract installation instructions
342 |     const installation = this.extractInstallation($);
343 |     
344 |     // Extract usage examples
345 |     const usage = this.extractUsage($);
346 |     
347 |     // Extract variant information
348 |     const props = this.extractVariants($, componentName);
349 |     
350 |     return {
351 |       name: componentName,
352 |       description,
353 |       url: `${this.SHADCN_DOCS_URL}/docs/components/${componentName}`,
354 |       sourceUrl,
355 |       installation: installation.trim(),
356 |       usage: usage.trim(),
357 |       props: Object.keys(props).length > 0 ? props : undefined,
358 |     };
359 |   }
360 | 
361 |   /**
362 |    * Extracts component description from the page
363 |    * @param $ Cheerio instance
364 |    * @returns Extracted description
365 |    */
366 |   private extractDescription($: cheerio.CheerioAPI): string {
367 |     let description = "";
368 |     const descriptionElement = $("h1").first().next("p");
369 |     if (descriptionElement.length > 0) {
370 |       // Get only text content, removing any JavaScript code
371 |       const clonedElement = descriptionElement.clone();
372 |       clonedElement.find("script").remove();
373 |       description = clonedElement.text().trim();
374 |     }
375 |     return description;
376 |   }
377 | 
378 |   /**
379 |    * Extracts installation instructions from the page
380 |    * @param $ Cheerio instance
381 |    * @returns Installation instructions
382 |    */
383 |   private extractInstallation($: cheerio.CheerioAPI): string {
384 |     let installation = "";
385 |     const installSection = $("h2").filter((_, el) => $(el).text().trim() === "Installation");
386 |     if (installSection.length > 0) {
387 |       // Find installation command
388 |       const codeBlock = installSection.nextAll("pre").first();
389 |       if (codeBlock.length > 0) {
390 |         installation = codeBlock.text().trim();
391 |       }
392 |     }
393 |     return installation;
394 |   }
395 | 
396 |   /**
397 |    * Extracts usage examples from the page
398 |    * @param $ Cheerio instance
399 |    * @returns Usage examples
400 |    */
401 |   private extractUsage($: cheerio.CheerioAPI): string {
402 |     let usage = "";
403 |     const usageSection = $("h2").filter((_, el) => $(el).text().trim() === "Usage");
404 |     if (usageSection.length > 0) {
405 |       const codeBlocks = usageSection.nextAll("pre");
406 |       if (codeBlocks.length > 0) {
407 |         codeBlocks.each((_, el) => {
408 |           usage += $(el).text().trim() + "\n\n";
409 |         });
410 |       }
411 |     }
412 |     return usage;
413 |   }
414 | 
415 |   /**
416 |    * Extracts variant information from the page
417 |    * @param $ Cheerio instance
418 |    * @param componentName Name of the component
419 |    * @returns Object containing variant properties
420 |    */
421 |   private extractVariants($: cheerio.CheerioAPI, componentName: string): Record<string, ComponentProp> {
422 |     const props: Record<string, ComponentProp> = {};
423 |     
424 |     // Extract variants from Examples section
425 |     const examplesSection = $("h2").filter((_, el) => $(el).text().trim() === "Examples");
426 |     if (examplesSection.length > 0) {
427 |       // Find each variant
428 |       const variantHeadings = examplesSection.nextAll("h3");
429 |       
430 |       variantHeadings.each((_, heading) => {
431 |         const variantName = $(heading).text().trim();
432 |         
433 |         // Get variant code example
434 |         let codeExample = "";
435 |         
436 |         // Find Code tab
437 |         const codeTab = $(heading).nextAll(".tabs-content").first();
438 |         if (codeTab.length > 0) {
439 |           const codeBlock = codeTab.find("pre");
440 |           if (codeBlock.length > 0) {
441 |             codeExample = codeBlock.text().trim();
442 |           }
443 |         }
444 |         
445 |         props[variantName] = {
446 |           type: "variant",
447 |           description: `${variantName} variant of the ${componentName} component`,
448 |           required: false,
449 |           example: codeExample
450 |         };
451 |       });
452 |     }
453 |     
454 |     return props;
455 |   }
456 | 
457 |   /**
458 |    * Handle the get_component_examples tool request
459 |    */
460 |   private async handleGetComponentExamples(args: any) {
461 |     const componentName = this.validateComponentName(args);
462 | 
463 |     try {
464 |       // Fetch component examples
465 |       const examples = await this.fetchComponentExamples(componentName);
466 |       return this.createSuccessResponse(examples);
467 |     } catch (error) {
468 |       this.handleAxiosError(error, `Component examples for "${componentName}"`);
469 |     }
470 |   }
471 | 
472 |   /**
473 |    * Fetches component examples from documentation and GitHub
474 |    * @param componentName Name of the component
475 |    * @returns Array of component examples
476 |    */
477 |   private async fetchComponentExamples(componentName: string): Promise<ComponentExample[]> {
478 |     const response = await this.axiosInstance.get(`${this.SHADCN_DOCS_URL}/docs/components/${componentName}`);
479 |     const $ = cheerio.load(response.data);
480 |     
481 |     const examples: ComponentExample[] = [];
482 |     
483 |     // Collect examples from different sources
484 |     this.collectGeneralCodeExamples($, examples);
485 |     this.collectSectionExamples($, "Usage", "Basic usage example", examples);
486 |     this.collectSectionExamples($, "Link", "Link usage example", examples);
487 |     await this.collectGitHubExamples(componentName, examples);
488 |     
489 |     return examples;
490 |   }
491 | 
492 |   /**
493 |    * Collects general code examples from the page
494 |    * @param $ Cheerio instance
495 |    * @param examples Array to add examples to
496 |    */
497 |   private collectGeneralCodeExamples($: cheerio.CheerioAPI, examples: ComponentExample[]): void {
498 |     const codeBlocks = $("pre");
499 |     codeBlocks.each((i, el) => {
500 |       const code = $(el).text().trim();
501 |       if (code) {
502 |         // Find heading before code block
503 |         let title = "Code Example " + (i + 1);
504 |         let description = "Code example";
505 |         
506 |         // Look for headings
507 |         let prevElement = $(el).prev();
508 |         while (prevElement.length && !prevElement.is("h1") && !prevElement.is("h2") && !prevElement.is("h3")) {
509 |           prevElement = prevElement.prev();
510 |         }
511 |         
512 |         if (prevElement.is("h2") || prevElement.is("h3")) {
513 |           title = prevElement.text().trim();
514 |           description = `${title} example`;
515 |         }
516 |         
517 |         examples.push({
518 |           title,
519 |           code,
520 |           description
521 |         });
522 |       }
523 |     });
524 |   }
525 | 
526 |   /**
527 |    * Collects examples from a specific section
528 |    * @param $ Cheerio instance
529 |    * @param sectionName Name of the section to collect from
530 |    * @param descriptionPrefix Prefix for the description
531 |    * @param examples Array to add examples to
532 |    */
533 |   private collectSectionExamples(
534 |     $: cheerio.CheerioAPI, 
535 |     sectionName: string, 
536 |     descriptionPrefix: string,
537 |     examples: ComponentExample[]
538 |   ): void {
539 |     const section = $("h2").filter((_, el) => $(el).text().trim() === sectionName);
540 |     if (section.length > 0) {
541 |       const codeBlocks = section.nextAll("pre");
542 |       codeBlocks.each((i, el) => {
543 |         const code = $(el).text().trim();
544 |         if (code) {
545 |           examples.push({
546 |             title: `${sectionName} Example ${i + 1}`,
547 |             code: code,
548 |             description: descriptionPrefix
549 |           });
550 |         }
551 |       });
552 |     }
553 |   }
554 | 
555 |   /**
556 |    * Collects examples from GitHub repository
557 |    * @param componentName Name of the component
558 |    * @param examples Array to add examples to
559 |    */
560 |   private async collectGitHubExamples(componentName: string, examples: ComponentExample[]): Promise<void> {
561 |     try {
562 |       const githubResponse = await this.axiosInstance.get(
563 |         `${this.SHADCN_RAW_GITHUB_URL}/apps/www/registry/default/example/${componentName}-demo.tsx`
564 |       );
565 |       
566 |       if (githubResponse.status === 200) {
567 |         examples.push({
568 |           title: "GitHub Demo Example",
569 |           code: githubResponse.data,
570 |         });
571 |       }
572 |     } catch (error) {
573 |       // Continue even if GitHub fetch fails
574 |       console.error(`Failed to fetch GitHub example for ${componentName}:`, error);
575 |     }
576 |   }
577 | 
578 |   /**
579 |    * Handle the search_components tool request
580 |    */
581 |   private async handleSearchComponents(args: any) {
582 |     const query = this.validateSearchQuery(args);
583 | 
584 |     try {
585 |       // Ensure components list is loaded
586 |       await this.ensureComponentsListLoaded();
587 |       
588 |       // Filter components matching the search query
589 |       const results = this.searchComponentsByQuery(query);
590 |       
591 |       return this.createSuccessResponse(results);
592 |     } catch (error) {
593 |       this.handleAxiosError(error, "Search failed");
594 |     }
595 |   }
596 | 
597 |   /**
598 |    * Ensures the components list is loaded in cache
599 |    * @throws McpError if components list cannot be loaded
600 |    */
601 |   private async ensureComponentsListLoaded(): Promise<void> {
602 |     if (!this.componentsListCache) {
603 |       await this.handleListComponents();
604 |     }
605 |     
606 |     if (!this.componentsListCache) {
607 |       throw new McpError(
608 |         ErrorCode.InternalError,
609 |         "Failed to load components list"
610 |       );
611 |     }
612 |   }
613 | 
614 |   /**
615 |    * Searches components by query string
616 |    * @param query Search query
617 |    * @returns Filtered components
618 |    */
619 |   private searchComponentsByQuery(query: string): ComponentInfo[] {
620 |     if (!this.componentsListCache) {
621 |       return [];
622 |     }
623 |     
624 |     return this.componentsListCache.filter(component => {
625 |       return (
626 |         component.name.includes(query) ||
627 |         component.description.toLowerCase().includes(query)
628 |       );
629 |     });
630 |   }
631 | 
632 |   /**
633 |    * Run the server
634 |    */
635 |   async run() {
636 |     const transport = new StdioServerTransport();
637 |     await this.server.connect(transport);
638 |     console.error("shadcn/ui MCP server running on stdio");
639 |   }
640 | }
641 | 
642 | // Create and run the server
643 | const server = new ShadcnUiServer();
644 | server.run().catch((error) => {
645 |   console.error("Server error:", error);
646 |   process.exit(1);
647 | });
648 | 
```