# 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 | ```