#
tokens: 5384/50000 5/5 files
lines: off (toggle) GitHub
raw markdown copy
# Directory Structure

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

# Files

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

```
node_modules/
build/
*.log
.env*
```

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

```markdown
# shadcn-ui MCP Server

MCP server for shadcn/ui component references

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.

## Features

### Tools

- `list_shadcn_components` - Get a list of all available shadcn/ui components
- `get_component_details` - Get detailed information about a specific component
- `get_component_examples` - Get usage examples for a specific component
- `search_components` - Search for components by keyword

### Functionality

This server scrapes and caches information from:
- The official shadcn/ui documentation site (https://ui.shadcn.com)
- The shadcn/ui GitHub repository

It provides structured data including:
- Component descriptions
- Installation instructions
- Usage examples
- Props and variants
- Code samples

## Development

Install dependencies:
```bash
npm install
```

Build the server:
```bash
npm run build
```

For development with auto-rebuild:
```bash
npm run watch
```

## Installation

### Claude Desktop Configuration

To use with Claude Desktop, add the server config:

On MacOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
On Windows: `%APPDATA%/Claude/claude_desktop_config.json`

#### Option 1: Using local build

```json
{
  "mcpServers": {
    "shadcn-ui-server": {
      "command": "/path/to/shadcn-ui-server/build/index.js"
    }
  }
}
```

#### Option 2: Using npx command

```json
{
  "mcpServers": {
    "shadcn-ui-server": {
      "command": "npx",
      "args": ["-y", "shadcn-ui-mcp-server"]
    }
  }
}
```

### Windsurf Configuration

Add this to your `./codeium/windsurf/model_config.json`:

```json
{
  "mcpServers": {
    "shadcn-ui-server": {
      "command": "npx",
      "args": ["-y", "shadcn-ui-mcp-server"]
    }
  }
}
```

### Cursor Configuration

Add this to your `.cursor/mcp.json`:

```json
{
  "mcpServers": {
    "shadcn-ui-server": {
      "command": "npx",
      "args": ["-y", "shadcn-ui-mcp-server"]
    }
  }
}
```

### Debugging

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:

```bash
npm run inspector
```

The Inspector will provide a URL to access debugging tools in your browser.

```

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

```json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "./build",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}

```

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

```json
{
  "name": "shadcn-ui-mcp-server",
  "version": "0.1.2",
  "description": "MCP server for shadcn/ui component references",
  "type": "module",
  "license": "MIT",
  "bin": {
    "shadcn-ui-server": "build/index.js"
  },
  "files": [
    "build"
  ],
  "scripts": {
    "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
    "prepare": "npm run build",
    "watch": "tsc --watch",
    "inspector": "npx @modelcontextprotocol/inspector build/index.js"
  },
  "dependencies": {
    "@modelcontextprotocol/sdk": "0.6.0",
    "axios": "^1.7.9",
    "cheerio": "^1.0.0-rc.12"
  },
  "devDependencies": {
    "@types/node": "^20.11.24",
    "typescript": "^5.3.3"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/ymadd/shadcn-ui-mcp-server.git"
  },
  "author": "ymadd",
  "bugs": {
    "url": "https://github.com/ymadd/shadcn-ui-mcp-server/issues"
  },
  "homepage": "https://github.com/ymadd/shadcn-ui-mcp-server#readme",
  "keywords": [
    "shadcn/ui",
    "MCP",
    "model context protocol",
    "component references"
  ]
}

```

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

```typescript
#!/usr/bin/env node

/**
 * MCP server for shadcn/ui component references
 * This server provides tools to:
 * - List all available shadcn/ui components
 * - Get detailed information about specific components
 * - Get usage examples for components
 * - Search for components by keyword
 */

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
  CallToolRequestSchema,
  ErrorCode,
  ListToolsRequestSchema,
  McpError,
} from "@modelcontextprotocol/sdk/types.js";
import axios from "axios";
import * as cheerio from "cheerio";

/**
 * Interface for component information
 */
interface ComponentInfo {
  name: string;
  description: string;
  url: string;
  sourceUrl?: string;
  apiReference?: string;
  installation?: string;
  usage?: string;
  props?: Record<string, ComponentProp>;
  examples?: ComponentExample[];
}

/**
 * Interface for component property information
 */
interface ComponentProp {
  type: string;
  description: string;
  required: boolean;
  default?: string;
  example?: string;
}

/**
 * Interface for component example
 */
interface ComponentExample {
  title: string;
  code: string;
  description?: string;
}

/**
 * ShadcnUiServer class that handles all the component reference functionality
 */
class ShadcnUiServer {
  private server: Server;
  private axiosInstance;
  private componentCache: Map<string, ComponentInfo> = new Map();
  private componentsListCache: ComponentInfo[] | null = null;
  private readonly SHADCN_DOCS_URL = "https://ui.shadcn.com";
  private readonly SHADCN_GITHUB_URL = "https://github.com/shadcn-ui/ui";
  private readonly SHADCN_RAW_GITHUB_URL = "https://raw.githubusercontent.com/shadcn-ui/ui/main";

  constructor() {
    this.server = new Server(
      {
        name: "shadcn-ui-server",
        version: "0.1.0",
      },
      {
        capabilities: {
          tools: {},
        },
      }
    );

    this.axiosInstance = axios.create({
      timeout: 10000,
      headers: {
        "User-Agent": "Mozilla/5.0 (compatible; ShadcnUiMcpServer/0.1.0)",
      },
    });
    
    this.setupToolHandlers();
    
    this.server.onerror = (error) => console.error("[MCP Error]", error);
    process.on("SIGINT", async () => {
      await this.server.close();
      process.exit(0);
    });
  }

  /**
   * Set up the tool handlers for the server
   */
  private setupToolHandlers() {
    this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
      tools: [
        {
          name: "list_shadcn_components",
          description: "Get a list of all available shadcn/ui components",
          inputSchema: {
            type: "object",
            properties: {},
            required: [],
          },
        },
        {
          name: "get_component_details",
          description: "Get detailed information about a specific shadcn/ui component",
          inputSchema: {
            type: "object",
            properties: {
              componentName: {
                type: "string",
                description: "Name of the shadcn/ui component (e.g., \"accordion\", \"button\")",
              },
            },
            required: ["componentName"],
          },
        },
        {
          name: "get_component_examples",
          description: "Get usage examples for a specific shadcn/ui component",
          inputSchema: {
            type: "object",
            properties: {
              componentName: {
                type: "string",
                description: "Name of the shadcn/ui component (e.g., \"accordion\", \"button\")",
              },
            },
            required: ["componentName"],
          },
        },
        {
          name: "search_components",
          description: "Search for shadcn/ui components by keyword",
          inputSchema: {
            type: "object",
            properties: {
              query: {
                type: "string",
                description: "Search query to find relevant components",
              },
            },
            required: ["query"],
          },
        },
      ],
    }));

    this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
      switch (request.params.name) {
        case "list_shadcn_components":
          return await this.handleListComponents();
        case "get_component_details":
          return await this.handleGetComponentDetails(request.params.arguments);
        case "get_component_examples":
          return await this.handleGetComponentExamples(request.params.arguments);
        case "search_components":
          return await this.handleSearchComponents(request.params.arguments);
        default:
          throw new McpError(
            ErrorCode.MethodNotFound,
            `Unknown tool: ${request.params.name}`
          );
      }
    });
  }

  /**
   * Handle the list_shadcn_components tool request
   */
  private async handleListComponents() {
    try {
      if (!this.componentsListCache) {
        // Fetch the list of components
        const response = await this.axiosInstance.get(`${this.SHADCN_DOCS_URL}/docs/components`);
        const $ = cheerio.load(response.data);
        
        const components: ComponentInfo[] = [];
        
        // Extract component links
        $("a").each((_, element) => {
          const link = $(element);
          const url = link.attr("href");
          
          if (url && url.startsWith("/docs/components/")) {
            const name = url.split("/").pop() || "";
            
            components.push({
              name,
              description: "", // Will be populated when fetching details
              url: `${this.SHADCN_DOCS_URL}${url}`,
            });
          }
        });
        
        this.componentsListCache = components;
      }
      
      return {
        content: [
          {
            type: "text",
            text: JSON.stringify(this.componentsListCache, null, 2),
          },
        ],
      };
    } catch (error) {
      if (axios.isAxiosError(error)) {
        throw new McpError(
          ErrorCode.InternalError,
          `Failed to fetch shadcn/ui components: ${error.message}`
        );
      }
      throw error;
    }
  }

  /**
   * Validates component name from arguments
   * @param args Arguments object
   * @returns Validated component name
   * @throws McpError if validation fails
   */
  private validateComponentName(args: any): string {
    if (!args?.componentName || typeof args.componentName !== "string") {
      throw new McpError(
        ErrorCode.InvalidParams,
        "Component name is required and must be a string"
      );
    }
    return args.componentName.toLowerCase();
  }

  /**
   * Validates search query from arguments
   * @param args Arguments object
   * @returns Validated search query
   * @throws McpError if validation fails
   */
  private validateSearchQuery(args: any): string {
    if (!args?.query || typeof args.query !== "string") {
      throw new McpError(
        ErrorCode.InvalidParams,
        "Search query is required and must be a string"
      );
    }
    return args.query.toLowerCase();
  }

  /**
   * Handles Axios errors consistently
   * @param error The caught error
   * @param context Context information for the error message
   * @throws McpError with appropriate error code and message
   */
  private handleAxiosError(error: unknown, context: string): never {
    if (axios.isAxiosError(error)) {
      if (error.response?.status === 404) {
        throw new McpError(
          ErrorCode.InvalidParams,
          `${context} not found`
        );
      } else {
        throw new McpError(
          ErrorCode.InternalError,
          `${context}: ${error.message}`
        );
      }
    }
    throw error;
  }

  /**
   * Creates a standardized success response
   * @param data Data to include in the response
   * @returns Formatted response object
   */
  private createSuccessResponse(data: any) {
    return {
      content: [
        {
          type: "text",
          text: JSON.stringify(data, null, 2),
        },
      ],
    };
  }

  /**
   * Handle the get_component_details tool request
   */
  private async handleGetComponentDetails(args: any) {
    const componentName = this.validateComponentName(args);

    try {
      // Check cache first
      if (this.componentCache.has(componentName)) {
        return this.createSuccessResponse(this.componentCache.get(componentName));
      }

      // Fetch component details
      const componentInfo = await this.fetchComponentDetails(componentName);
      
      // Save to cache
      this.componentCache.set(componentName, componentInfo);
      
      return this.createSuccessResponse(componentInfo);
    } catch (error) {
      this.handleAxiosError(error, `Component "${componentName}"`);
    }
  }

  /**
   * Fetches component details from the shadcn/ui documentation
   * @param componentName Name of the component to fetch
   * @returns Component information
   */
  private async fetchComponentDetails(componentName: string): Promise<ComponentInfo> {
    const response = await this.axiosInstance.get(`${this.SHADCN_DOCS_URL}/docs/components/${componentName}`);
    const $ = cheerio.load(response.data);
    
    // Extract component information
    const title = $("h1").first().text().trim();
    
    // Extract description properly
    const description = this.extractDescription($);
    
    // Extract GitHub source code link
    const sourceUrl = `${this.SHADCN_GITHUB_URL}/tree/main/apps/www/registry/default/ui/${componentName}`;
    
    // Extract installation instructions
    const installation = this.extractInstallation($);
    
    // Extract usage examples
    const usage = this.extractUsage($);
    
    // Extract variant information
    const props = this.extractVariants($, componentName);
    
    return {
      name: componentName,
      description,
      url: `${this.SHADCN_DOCS_URL}/docs/components/${componentName}`,
      sourceUrl,
      installation: installation.trim(),
      usage: usage.trim(),
      props: Object.keys(props).length > 0 ? props : undefined,
    };
  }

  /**
   * Extracts component description from the page
   * @param $ Cheerio instance
   * @returns Extracted description
   */
  private extractDescription($: cheerio.CheerioAPI): string {
    let description = "";
    const descriptionElement = $("h1").first().next("p");
    if (descriptionElement.length > 0) {
      // Get only text content, removing any JavaScript code
      const clonedElement = descriptionElement.clone();
      clonedElement.find("script").remove();
      description = clonedElement.text().trim();
    }
    return description;
  }

  /**
   * Extracts installation instructions from the page
   * @param $ Cheerio instance
   * @returns Installation instructions
   */
  private extractInstallation($: cheerio.CheerioAPI): string {
    let installation = "";
    const installSection = $("h2").filter((_, el) => $(el).text().trim() === "Installation");
    if (installSection.length > 0) {
      // Find installation command
      const codeBlock = installSection.nextAll("pre").first();
      if (codeBlock.length > 0) {
        installation = codeBlock.text().trim();
      }
    }
    return installation;
  }

  /**
   * Extracts usage examples from the page
   * @param $ Cheerio instance
   * @returns Usage examples
   */
  private extractUsage($: cheerio.CheerioAPI): string {
    let usage = "";
    const usageSection = $("h2").filter((_, el) => $(el).text().trim() === "Usage");
    if (usageSection.length > 0) {
      const codeBlocks = usageSection.nextAll("pre");
      if (codeBlocks.length > 0) {
        codeBlocks.each((_, el) => {
          usage += $(el).text().trim() + "\n\n";
        });
      }
    }
    return usage;
  }

  /**
   * Extracts variant information from the page
   * @param $ Cheerio instance
   * @param componentName Name of the component
   * @returns Object containing variant properties
   */
  private extractVariants($: cheerio.CheerioAPI, componentName: string): Record<string, ComponentProp> {
    const props: Record<string, ComponentProp> = {};
    
    // Extract variants from Examples section
    const examplesSection = $("h2").filter((_, el) => $(el).text().trim() === "Examples");
    if (examplesSection.length > 0) {
      // Find each variant
      const variantHeadings = examplesSection.nextAll("h3");
      
      variantHeadings.each((_, heading) => {
        const variantName = $(heading).text().trim();
        
        // Get variant code example
        let codeExample = "";
        
        // Find Code tab
        const codeTab = $(heading).nextAll(".tabs-content").first();
        if (codeTab.length > 0) {
          const codeBlock = codeTab.find("pre");
          if (codeBlock.length > 0) {
            codeExample = codeBlock.text().trim();
          }
        }
        
        props[variantName] = {
          type: "variant",
          description: `${variantName} variant of the ${componentName} component`,
          required: false,
          example: codeExample
        };
      });
    }
    
    return props;
  }

  /**
   * Handle the get_component_examples tool request
   */
  private async handleGetComponentExamples(args: any) {
    const componentName = this.validateComponentName(args);

    try {
      // Fetch component examples
      const examples = await this.fetchComponentExamples(componentName);
      return this.createSuccessResponse(examples);
    } catch (error) {
      this.handleAxiosError(error, `Component examples for "${componentName}"`);
    }
  }

  /**
   * Fetches component examples from documentation and GitHub
   * @param componentName Name of the component
   * @returns Array of component examples
   */
  private async fetchComponentExamples(componentName: string): Promise<ComponentExample[]> {
    const response = await this.axiosInstance.get(`${this.SHADCN_DOCS_URL}/docs/components/${componentName}`);
    const $ = cheerio.load(response.data);
    
    const examples: ComponentExample[] = [];
    
    // Collect examples from different sources
    this.collectGeneralCodeExamples($, examples);
    this.collectSectionExamples($, "Usage", "Basic usage example", examples);
    this.collectSectionExamples($, "Link", "Link usage example", examples);
    await this.collectGitHubExamples(componentName, examples);
    
    return examples;
  }

  /**
   * Collects general code examples from the page
   * @param $ Cheerio instance
   * @param examples Array to add examples to
   */
  private collectGeneralCodeExamples($: cheerio.CheerioAPI, examples: ComponentExample[]): void {
    const codeBlocks = $("pre");
    codeBlocks.each((i, el) => {
      const code = $(el).text().trim();
      if (code) {
        // Find heading before code block
        let title = "Code Example " + (i + 1);
        let description = "Code example";
        
        // Look for headings
        let prevElement = $(el).prev();
        while (prevElement.length && !prevElement.is("h1") && !prevElement.is("h2") && !prevElement.is("h3")) {
          prevElement = prevElement.prev();
        }
        
        if (prevElement.is("h2") || prevElement.is("h3")) {
          title = prevElement.text().trim();
          description = `${title} example`;
        }
        
        examples.push({
          title,
          code,
          description
        });
      }
    });
  }

  /**
   * Collects examples from a specific section
   * @param $ Cheerio instance
   * @param sectionName Name of the section to collect from
   * @param descriptionPrefix Prefix for the description
   * @param examples Array to add examples to
   */
  private collectSectionExamples(
    $: cheerio.CheerioAPI, 
    sectionName: string, 
    descriptionPrefix: string,
    examples: ComponentExample[]
  ): void {
    const section = $("h2").filter((_, el) => $(el).text().trim() === sectionName);
    if (section.length > 0) {
      const codeBlocks = section.nextAll("pre");
      codeBlocks.each((i, el) => {
        const code = $(el).text().trim();
        if (code) {
          examples.push({
            title: `${sectionName} Example ${i + 1}`,
            code: code,
            description: descriptionPrefix
          });
        }
      });
    }
  }

  /**
   * Collects examples from GitHub repository
   * @param componentName Name of the component
   * @param examples Array to add examples to
   */
  private async collectGitHubExamples(componentName: string, examples: ComponentExample[]): Promise<void> {
    try {
      const githubResponse = await this.axiosInstance.get(
        `${this.SHADCN_RAW_GITHUB_URL}/apps/www/registry/default/example/${componentName}-demo.tsx`
      );
      
      if (githubResponse.status === 200) {
        examples.push({
          title: "GitHub Demo Example",
          code: githubResponse.data,
        });
      }
    } catch (error) {
      // Continue even if GitHub fetch fails
      console.error(`Failed to fetch GitHub example for ${componentName}:`, error);
    }
  }

  /**
   * Handle the search_components tool request
   */
  private async handleSearchComponents(args: any) {
    const query = this.validateSearchQuery(args);

    try {
      // Ensure components list is loaded
      await this.ensureComponentsListLoaded();
      
      // Filter components matching the search query
      const results = this.searchComponentsByQuery(query);
      
      return this.createSuccessResponse(results);
    } catch (error) {
      this.handleAxiosError(error, "Search failed");
    }
  }

  /**
   * Ensures the components list is loaded in cache
   * @throws McpError if components list cannot be loaded
   */
  private async ensureComponentsListLoaded(): Promise<void> {
    if (!this.componentsListCache) {
      await this.handleListComponents();
    }
    
    if (!this.componentsListCache) {
      throw new McpError(
        ErrorCode.InternalError,
        "Failed to load components list"
      );
    }
  }

  /**
   * Searches components by query string
   * @param query Search query
   * @returns Filtered components
   */
  private searchComponentsByQuery(query: string): ComponentInfo[] {
    if (!this.componentsListCache) {
      return [];
    }
    
    return this.componentsListCache.filter(component => {
      return (
        component.name.includes(query) ||
        component.description.toLowerCase().includes(query)
      );
    });
  }

  /**
   * Run the server
   */
  async run() {
    const transport = new StdioServerTransport();
    await this.server.connect(transport);
    console.error("shadcn/ui MCP server running on stdio");
  }
}

// Create and run the server
const server = new ShadcnUiServer();
server.run().catch((error) => {
  console.error("Server error:", error);
  process.exit(1);
});

```