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

```
├── .gitignore
├── index.mjs
├── package-lock.json
├── package.json
├── README.md
├── scripts
│   ├── extract-docs.mjs
│   └── test-server.mjs
└── tsconfig.json
```

# Files

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

```
1 | node_modules
2 | 
```

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

```markdown
  1 | # Ant Design Components Model Context Protocol Server
  2 | 
  3 | A Model Context Protocol (MCP) server that exposes Ant Design component documentation to Large Language Models (LLMs) like Claude. This server allows an LLM to explore and understand Ant Design components through a set of specialized tools.
  4 | 
  5 | ## Features
  6 | 
  7 | - Easy to use - no need to clone the entire Ant Design repository
  8 | - Pre-extracted component documentation for faster startup
  9 | - List all available Ant Design components
 10 | - Get detailed component documentation including descriptions and usage
 11 | - View component props and API definitions
 12 | - Browse code examples for specific components
 13 | - Search for components by name or functionality
 14 | 
 15 | ## Initial Setup
 16 | 
 17 | Before using the MCP server for the first time, you need to extract the documentation from the Ant Design repository:
 18 | 
 19 | ```bash
 20 | # First, clone the Ant Design repository (can be temporary)
 21 | git clone https://github.com/ant-design/ant-design.git
 22 | 
 23 | # Extract documentation
 24 | cd mcp-antd-components
 25 | npm run extract   # Uses the default ./ant-design path
 26 | # OR
 27 | node scripts/extract-docs.mjs /path/to/ant-design  # For a custom path
 28 | 
 29 | # After extraction is complete, the Ant Design repo can be deleted if desired
 30 | ```
 31 | 
 32 | This will create a `data` directory with all the extracted component documentation, which the MCP server will use.
 33 | 
 34 | ### Testing the Server
 35 | 
 36 | To verify that everything is working correctly, you can run the test script:
 37 | 
 38 | ```bash
 39 | npm test
 40 | # OR
 41 | node scripts/test-server.mjs
 42 | ```
 43 | 
 44 | This will run the MCP server and test all available tools with sample queries.
 45 | 
 46 | ## Usage
 47 | 
 48 | ### Command Line
 49 | 
 50 | Run the MCP server:
 51 | 
 52 | ```bash
 53 | # Run server with pre-extracted data
 54 | npm start
 55 | # OR
 56 | npx -y mcp-antd-components
 57 | ```
 58 | 
 59 | ### Claude Desktop Integration
 60 | 
 61 | To use this MCP server with Claude Desktop, edit your `claude_desktop_config.json` configuration file:
 62 | 
 63 | ```json
 64 | {
 65 |   "mcpServers": {
 66 |     "Ant Design Components": {
 67 |       "command": "npx",
 68 |       "args": ["-y", "mcp-antd-components"]
 69 |     }
 70 |   }
 71 | }
 72 | ```
 73 | 
 74 | Location of the configuration file:
 75 | 
 76 | - macOS/Linux: `~/Library/Application Support/Claude/claude_desktop_config.json`
 77 | - Windows: `$env:AppData\Claude\claude_desktop_config.json`
 78 | 
 79 | ### Claude Code Integration
 80 | 
 81 | To use this MCP server with Claude Code CLI, follow these steps:
 82 | 
 83 | 1. **Add the Ant Design Components MCP server to Claude Code**
 84 | 
 85 |    ```bash
 86 |    # Basic syntax
 87 |    claude mcp add antd-components npx -y mcp-antd-components
 88 |    ```
 89 | 
 90 | 2. **Verify the MCP server is registered**
 91 | 
 92 |    ```bash
 93 |    # List all configured servers
 94 |    claude mcp list
 95 | 
 96 |    # Get details for your Ant Design components server
 97 |    claude mcp get antd-components
 98 |    ```
 99 | 
100 | 3. **Remove the server if needed**
101 | 
102 |    ```bash
103 |    claude mcp remove antd-components
104 |    ```
105 | 
106 | 4. **Use the tool in Claude Code**
107 | 
108 |    Once configured, you can invoke the tool in your Claude Code session by asking questions about Ant Design components.
109 | 
110 | **Tips:**
111 | 
112 | - Use the `-s` or `--scope` flag with `project` (default) or `global` to specify where the configuration is stored
113 | 
114 | ## MCP Tools
115 | 
116 | The server provides the following tools for LLMs to interact with Ant Design component documentation:
117 | 
118 | - `list-components`: Lists all available Ant Design components in PascalCase format (e.g., Button, DatePicker)
119 | - `get-component-props`: Gets the props and API documentation for a specific component (use PascalCase names like "Button")
120 | - `get-component-docs`: Gets detailed documentation for a specific component (use PascalCase names like "DatePicker")
121 | - `list-component-examples`: Lists all examples available for a specific component (use PascalCase names like "Table")
122 | - `get-component-example`: Gets the code for a specific component example (component name in PascalCase)
123 | - `search-components`: Search for components by name pattern (works with PascalCase patterns)
124 | 
125 | ## Examples
126 | 
127 | Example queries to try:
128 | 
129 | ```
130 | What components are available in Ant Design?
131 | Show me the documentation for the Button component.
132 | What props does the Table component accept?
133 | Show me code examples for the Modal component.
134 | Get the example "basic" for the Form component.
135 | Find components related to Input or Form elements.
136 | ```
137 | 
138 | Note: Component names are provided in PascalCase (e.g., Button, DatePicker, Table) to match React component naming conventions, even though the internal directory structure uses kebab-case (e.g., button, date-picker, table).
139 | 
140 | ## How It Works
141 | 
142 | The `scripts/extract-docs.mjs` script extracts documentation from the Ant Design repository and saves it to the `data` directory. This includes:
143 | 
144 | - Component documentation (markdown)
145 | - API/props documentation
146 | - Example code
147 | - Common props documentation
148 | 
149 | This approach has several advantages:
150 | 1. Users don't need to clone the entire Ant Design repository
151 | 2. Faster startup time for the MCP server
152 | 3. Smaller package size
153 | 4. Easier to update when new versions are released
154 | 
155 | To update the documentation for a new version of Ant Design, simply run the extraction script again with the updated repository.
```

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

```json
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "ES2020",
 4 |     "module": "NodeNext",
 5 |     "moduleResolution": "NodeNext",
 6 |     "strict": true,
 7 |     "skipLibCheck": true,
 8 |     "forceConsistentCasingInFileNames": true,
 9 |     "esModuleInterop": true
10 |   },
11 |   "include": [
12 |     "*.mjs",
13 |     "*.ts"
14 |   ]
15 | }
```

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

```json
 1 | {
 2 |   "name": "mcp-antd-components",
 3 |   "version": "0.0.1",
 4 |   "type": "module",
 5 |   "sideEffects": false,
 6 |   "main": "index.mjs",
 7 |   "description": "Model Context Protocol server for Ant Design components",
 8 |   "bin": {
 9 |     "mcp-antd-components": "index.mjs"
10 |   },
11 |   "scripts": {
12 |     "start": "node index.mjs",
13 |     "help": "node index.mjs --help",
14 |     "typecheck": "true",
15 |     "test": "node scripts/test-server.mjs",
16 |     "extract": "node scripts/extract-docs.mjs ./ant-design"
17 |   },
18 |   "keywords": [
19 |     "mcp",
20 |     "antd",
21 |     "ant design",
22 |     "model context protocol",
23 |     "ui components"
24 |   ],
25 |   "author": "Hannes Junnila",
26 |   "license": "MIT",
27 |   "dependencies": {
28 |     "@modelcontextprotocol/sdk": "^1.7.0",
29 |     "zod": "^3.24.2"
30 |   },
31 |   "engines": {
32 |     "node": ">=16.0.0"
33 |   }
34 | }
```

--------------------------------------------------------------------------------
/scripts/test-server.mjs:
--------------------------------------------------------------------------------

```
  1 | #!/usr/bin/env node
  2 | /**
  3 |  * This script tests the MCP Ant Design Components server
  4 |  * using the official MCP SDK client. It runs through all the available tools
  5 |  * and displays sample output for each.
  6 |  *
  7 |  * Usage:
  8 |  *   node scripts/test-server.mjs
  9 |  */
 10 | import { Client } from "@modelcontextprotocol/sdk/client/index.js";
 11 | import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
 12 | import { dirname, resolve } from "path";
 13 | import { fileURLToPath } from "url";
 14 | 
 15 | const __dirname = dirname(fileURLToPath(import.meta.url));
 16 | const rootDir = resolve(__dirname, "..");
 17 | 
 18 | // Set up the MCP client to communicate with our server
 19 | const transport = new StdioClientTransport({
 20 |   command: "node",
 21 |   args: [resolve(rootDir, "index.mjs")],
 22 | });
 23 | 
 24 | const client = new Client({
 25 |   name: "antd-components-client",
 26 |   version: "1.0.0",
 27 | });
 28 | 
 29 | // Connect to the server
 30 | console.log("Connecting to MCP server...");
 31 | await client.connect(transport);
 32 | console.log("Connected to MCP server successfully!");
 33 | 
 34 | // Run example tool calls
 35 | try {
 36 |   // List components
 37 |   console.log("\n--- LISTING COMPONENTS ---");
 38 |   const components = await client.callTool({
 39 |     name: "list-components",
 40 |     arguments: {},
 41 |   });
 42 |   console.log(components.content[0].text);
 43 | 
 44 |   // Get component documentation
 45 |   console.log("\n--- GET COMPONENT DOCUMENTATION ---");
 46 |   const docs = await client.callTool({
 47 |     name: "get-component-docs",
 48 |     arguments: {
 49 |       componentName: "Button", // Using PascalCase
 50 |     },
 51 |   });
 52 |   console.log(docs.content[0].text);
 53 | 
 54 |   // Get component props
 55 |   console.log("\n--- GET COMPONENT PROPS ---");
 56 |   const props = await client.callTool({
 57 |     name: "get-component-props",
 58 |     arguments: {
 59 |       componentName: "Button", // Using PascalCase
 60 |     },
 61 |   });
 62 |   console.log(props.content[0].text);
 63 | 
 64 |   // List component examples
 65 |   console.log("\n--- LIST COMPONENT EXAMPLES ---");
 66 |   const examples = await client.callTool({
 67 |     name: "list-component-examples",
 68 |     arguments: {
 69 |       componentName: "Button", // Using PascalCase
 70 |     },
 71 |   });
 72 |   console.log(examples.content[0].text);
 73 | 
 74 |   // Get component example
 75 |   console.log("\n--- GET COMPONENT EXAMPLE ---");
 76 |   const example = await client.callTool({
 77 |     name: "get-component-example",
 78 |     arguments: {
 79 |       componentName: "Button", // Using PascalCase
 80 |       exampleName: "basic",
 81 |     },
 82 |   });
 83 |   console.log(example.content[0].text);
 84 | 
 85 |   // Search components
 86 |   console.log("\n--- SEARCH COMPONENTS ---");
 87 |   const searchResults = await client.callTool({
 88 |     name: "search-components",
 89 |     arguments: {
 90 |       pattern: "Button|Input", // Using PascalCase
 91 |     },
 92 |   });
 93 |   console.log(searchResults.content[0].text);
 94 | } catch (error) {
 95 |   console.error("Error during testing:", error);
 96 | } finally {
 97 |   // Close the connection
 98 |   await client.close();
 99 |   console.log("\nTests completed, disconnected from server.");
100 | }
101 | 
```

--------------------------------------------------------------------------------
/scripts/extract-docs.mjs:
--------------------------------------------------------------------------------

```
  1 | #!/usr/bin/env node
  2 | /**
  3 |  * This script extracts component documentation from the Ant Design repository
  4 |  * and saves it to a local data directory for use by the MCP server.
  5 |  *
  6 |  * Usage:
  7 |  *   node extract-docs.mjs [path/to/ant-design]
  8 |  *
  9 |  * If path is not provided, it defaults to ./ant-design
 10 |  */
 11 | import { existsSync } from "node:fs";
 12 | import { mkdir, readFile, readdir, writeFile } from "node:fs/promises";
 13 | import { dirname, join, resolve } from "node:path";
 14 | import { fileURLToPath } from "node:url";
 15 | 
 16 | // Get the directory of the current script
 17 | const __dirname = dirname(fileURLToPath(import.meta.url));
 18 | const dataDir = resolve(__dirname, "../data");
 19 | 
 20 | // ==================================
 21 | // Utility functions
 22 | // ==================================
 23 | 
 24 | // Convert snake-case to PascalCase
 25 | const toPascalCase = (str) => {
 26 |   return str
 27 |     .split("-")
 28 |     .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
 29 |     .join("");
 30 | };
 31 | 
 32 | // Remove YAML frontmatter from markdown
 33 | const removeFrontmatter = (content) => {
 34 |   return content.replace(/^---\n([\s\S]*?)\n---\n/, "");
 35 | };
 36 | 
 37 | // Extract API section from markdown
 38 | const extractApiSection = (markdown) => {
 39 |   // Find the API section start
 40 |   const apiIndex = markdown.indexOf("\n## API\n");
 41 | 
 42 |   if (apiIndex !== -1) {
 43 |     // Find the next heading at the same level (starts with ## but not ###)
 44 |     const startPos = apiIndex + 1; // Skip the newline before ## API
 45 |     let endPos = markdown.length;
 46 | 
 47 |     // Find the next ## heading (but not ###+)
 48 |     const nextHeadingMatch = markdown.slice(startPos).match(/\n## [^#]/);
 49 |     if (nextHeadingMatch) {
 50 |       endPos = startPos + nextHeadingMatch.index;
 51 |     }
 52 | 
 53 |     // Extract the full API section
 54 |     return markdown.slice(startPos, endPos).trim();
 55 |   }
 56 | 
 57 |   return null;
 58 | };
 59 | 
 60 | // Extract examples with descriptions from markdown content
 61 | const extractExamples = (markdown) => {
 62 |   // Look for code snippets that reference demo files with descriptions
 63 |   const codeRefs = [
 64 |     ...markdown.matchAll(/<code src="\.\/demo\/([^"]+)\.tsx"(?:\s+[^>]*)?>(.*?)<\/code>/g),
 65 |   ];
 66 | 
 67 |   if (codeRefs && codeRefs.length > 0) {
 68 |     return codeRefs
 69 |       .filter((match) => !match[1].startsWith("debug-") && !match[1].startsWith("_"))
 70 |       .map((match) => ({
 71 |         name: match[1],
 72 |         description: match[2]?.trim() || match[1], // Use example name if no description
 73 |       }));
 74 |   }
 75 | 
 76 |   return [];
 77 | };
 78 | 
 79 | // ==================================
 80 | // Main extraction functions
 81 | // ==================================
 82 | 
 83 | // Process a component's documentation and examples
 84 | async function processComponent(componentsPath, dirName) {
 85 |   const componentPath = join(componentsPath, dirName);
 86 |   const indexMdPath = join(componentPath, "index.en-US.md");
 87 |   const demoPath = join(componentPath, "demo");
 88 | 
 89 |   if (!existsSync(indexMdPath)) {
 90 |     console.log(`⚠️ Skipping ${dirName} - no documentation found`);
 91 |     return null;
 92 |   }
 93 | 
 94 |   // Initialize component data
 95 |   const componentName = toPascalCase(dirName);
 96 |   console.log(`📝 Processing ${componentName}...`);
 97 | 
 98 |   const componentData = {
 99 |     name: componentName,
100 |     dirName: dirName,
101 |     documentation: null,
102 |     apiSection: null,
103 |     examples: {},
104 |   };
105 | 
106 |   try {
107 |     // Read and parse documentation
108 |     const docContent = await readFile(indexMdPath, "utf-8");
109 |     componentData.documentation = removeFrontmatter(docContent);
110 |     componentData.apiSection = extractApiSection(componentData.documentation)?.replace(
111 |       "Common props ref:[Common props](/docs/react/common-props)\n",
112 |       "",
113 |     );
114 | 
115 |     // Extract examples with descriptions from documentation
116 |     componentData.examplesInfo = extractExamples(componentData.documentation);
117 | 
118 |     // Read example files from the demo directory
119 |     if (existsSync(demoPath)) {
120 |       // Get all example files from the directory
121 |       const demoFiles = await readdir(demoPath, { withFileTypes: true });
122 |       const exampleFiles = demoFiles.filter(
123 |         (file) =>
124 |           !file.isDirectory() &&
125 |           (file.name.endsWith(".tsx") || file.name.endsWith(".jsx")) &&
126 |           !file.name.startsWith("_") &&
127 |           !file.name.startsWith("debug-"),
128 |       );
129 | 
130 |       // Process each example file
131 |       for (const exampleFile of exampleFiles) {
132 |         const exampleName = exampleFile.name.replace(/\.(tsx|jsx)$/, "");
133 |         const examplePath = join(demoPath, exampleFile.name);
134 | 
135 |         try {
136 |           componentData.examples[exampleName] = await readFile(examplePath, "utf-8");
137 |         } catch (error) {
138 |           console.error(`  ❌ Error reading example ${exampleName}:`, error.message);
139 |         }
140 |       }
141 | 
142 |       console.log(`  ✓ Found ${Object.keys(componentData.examples).length} examples`);
143 |     } else {
144 |       console.log(`  ℹ️ No examples directory for ${componentName}`);
145 |     }
146 | 
147 |     return componentData;
148 |   } catch (error) {
149 |     console.error(`  ❌ Error processing ${componentName}:`, error.message);
150 |     return null;
151 |   }
152 | }
153 | 
154 | // Main function to process all components and export data
155 | async function extractAllData(antdRepoPath) {
156 |   // Ensure the data directory exists
157 |   await mkdir(dataDir, { recursive: true });
158 | 
159 |   console.log(`🔍 Extracting documentation from ${antdRepoPath}...`);
160 |   const componentsPath = join(antdRepoPath, "components");
161 | 
162 |   if (!existsSync(componentsPath)) {
163 |     console.error(`❌ Error: ${antdRepoPath} does not appear to be an Ant Design repository.`);
164 |     console.error("The 'components' directory was not found.");
165 |     process.exit(1);
166 |   }
167 | 
168 |   // Read all component directories
169 |   const entries = await readdir(componentsPath, { withFileTypes: true });
170 |   const componentDirs = entries.filter(
171 |     (entry) =>
172 |       entry.isDirectory() &&
173 |       !entry.name.startsWith(".") &&
174 |       !entry.name.startsWith("_") &&
175 |       entry.name !== "locale" &&
176 |       entry.name !== "style" &&
177 |       entry.name !== "version",
178 |   );
179 | 
180 |   console.log(`📊 Found ${componentDirs.length} potential components`);
181 | 
182 |   // Process each component
183 |   const components = {};
184 |   let processedCount = 0;
185 | 
186 |   for (const entry of componentDirs) {
187 |     const componentData = await processComponent(componentsPath, entry.name);
188 |     if (componentData) {
189 |       components[componentData.name] = componentData;
190 |       processedCount++;
191 |     }
192 |   }
193 | 
194 |   console.log(`✅ Successfully processed ${processedCount} of ${componentDirs.length} components`);
195 | 
196 |   // Save the data
197 |   const metaData = {
198 |     generatedAt: new Date().toISOString(),
199 |     componentCount: processedCount,
200 |     version: "1.0.0",
201 |   };
202 | 
203 |   // Write component index (just names and dirNames)
204 |   const componentsIndex = Object.values(components).map((c) => ({
205 |     name: c.name,
206 |     dirName: c.dirName,
207 |   }));
208 | 
209 |   await writeFile(join(dataDir, "components-index.json"), JSON.stringify(componentsIndex, null, 2));
210 |   console.log(`💾 Saved components index to components-index.json`);
211 | 
212 |   // Write metadata
213 |   await writeFile(join(dataDir, "metadata.json"), JSON.stringify(metaData, null, 2));
214 |   console.log(`💾 Saved metadata to metadata.json`);
215 | 
216 |   // Create components directory
217 |   const componentsDataDir = join(dataDir, "components");
218 |   await mkdir(componentsDataDir, { recursive: true });
219 | 
220 |   // Write individual component files
221 |   for (const componentData of Object.values(components)) {
222 |     // Create a directory for the component
223 |     const componentDir = join(componentsDataDir, componentData.dirName);
224 |     await mkdir(componentDir, { recursive: true });
225 | 
226 |     // Write documentation
227 |     await writeFile(join(componentDir, "docs.md"), componentData.documentation);
228 | 
229 |     // Write API section if available
230 |     if (componentData.apiSection) {
231 |       await writeFile(join(componentDir, "api.md"), componentData.apiSection);
232 |     }
233 | 
234 |     // Write examples
235 |     if (Object.keys(componentData.examples).length > 0) {
236 |       const examplesDir = join(componentDir, "examples");
237 |       await mkdir(examplesDir, { recursive: true });
238 | 
239 |       // Create a markdown file with example descriptions
240 |       if (componentData.examplesInfo && componentData.examplesInfo.length > 0) {
241 |         let examplesMarkdown = "## Examples\n\n";
242 | 
243 |         componentData.examplesInfo.forEach((example) => {
244 |           examplesMarkdown += `- **${example.name}**: ${example.description}\n`;
245 |         });
246 | 
247 |         await writeFile(join(componentDir, "examples.md"), examplesMarkdown);
248 |       }
249 | 
250 |       for (const [exampleName, exampleCode] of Object.entries(componentData.examples)) {
251 |         // Determine if it's TSX or JSX based on content
252 |         const extension = exampleCode.includes("React.FC") ? ".tsx" : ".jsx";
253 |         await writeFile(join(examplesDir, `${exampleName}${extension}`), exampleCode);
254 |       }
255 |     }
256 |   }
257 | 
258 |   console.log(`🎉 Documentation extraction complete! Data saved to ${dataDir}`);
259 | }
260 | 
261 | // Parse command line arguments
262 | const args = process.argv.slice(2);
263 | const antdRepoArg = args[0];
264 | 
265 | // Default to ./ant-design if no argument provided
266 | const antdRepoPath = resolve(antdRepoArg ?? "./ant-design");
267 | 
268 | // Run the extraction
269 | extractAllData(antdRepoPath).catch((error) => {
270 |   console.error("❌ Fatal error:", error);
271 |   process.exit(1);
272 | });
273 | 
```

--------------------------------------------------------------------------------
/index.mjs:
--------------------------------------------------------------------------------

```
  1 | #!/usr/bin/env node
  2 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
  3 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
  4 | import { Console } from "node:console";
  5 | import { existsSync } from "node:fs";
  6 | import { readFile } from "node:fs/promises";
  7 | import { dirname, join, resolve } from "node:path";
  8 | import { fileURLToPath } from "node:url";
  9 | import { z } from "zod";
 10 | 
 11 | // Get the directory of the current script
 12 | const __dirname = dirname(fileURLToPath(import.meta.url));
 13 | globalThis.console = new Console(process.stderr);
 14 | 
 15 | // Path to the pre-extracted data
 16 | const dataDir = resolve(__dirname, "data");
 17 | const componentsDir = join(dataDir, "components");
 18 | 
 19 | // Check if the data directory exists
 20 | if (!existsSync(dataDir) || !existsSync(componentsDir)) {
 21 |   console.error(`Error: Data directory not found at ${dataDir}`);
 22 |   console.error("Please run the extraction script first:");
 23 |   console.error("  node scripts/extract-docs.mjs [path/to/ant-design]");
 24 |   process.exit(1);
 25 | }
 26 | 
 27 | // Check if the components index exists
 28 | const componentsIndexPath = join(dataDir, "components-index.json");
 29 | if (!existsSync(componentsIndexPath)) {
 30 |   console.error(`Error: Components index not found at ${componentsIndexPath}`);
 31 |   console.error("Please run the extraction script first.");
 32 |   process.exit(1);
 33 | }
 34 | 
 35 | // Initialize the MCP server
 36 | const server = new McpServer({
 37 |   name: "Ant Design Components",
 38 |   version: "1.0.0",
 39 |   description: "Provides documentation and examples for Ant Design components",
 40 | });
 41 | 
 42 | // ===============================================
 43 | // Utility functions
 44 | // ===============================================
 45 | 
 46 | // Load components index from the extracted data
 47 | async function loadComponentsIndex() {
 48 |   try {
 49 |     const indexData = await readFile(componentsIndexPath, "utf-8");
 50 |     return JSON.parse(indexData);
 51 |   } catch (error) {
 52 |     console.error(`Error loading components index: ${error.message}`);
 53 |     return [];
 54 |   }
 55 | }
 56 | 
 57 | // Find a component by name (case-insensitive)
 58 | async function findComponentByName(componentName) {
 59 |   const components = await loadComponentsIndex();
 60 |   return components.find(
 61 |     (c) =>
 62 |       c.name.toLowerCase() === componentName.toLowerCase() ||
 63 |       c.dirName.toLowerCase() === componentName.toLowerCase(),
 64 |   );
 65 | }
 66 | 
 67 | // Find components matching a pattern
 68 | async function findComponentsByPattern(pattern) {
 69 |   const components = await loadComponentsIndex();
 70 |   const regexPattern = new RegExp(pattern, "i");
 71 | 
 72 |   return components.filter((c) => regexPattern.test(c.name) || regexPattern.test(c.dirName));
 73 | }
 74 | 
 75 | // ===============================================
 76 | // Data fetching functions
 77 | // ===============================================
 78 | 
 79 | // Get component markdown documentation
 80 | const getComponentDocumentation = async (componentName) => {
 81 |   const component = await findComponentByName(componentName);
 82 | 
 83 |   if (!component) {
 84 |     return `Documentation for component "${componentName}" not found`;
 85 |   }
 86 | 
 87 |   const docPath = join(componentsDir, component.dirName, "docs.md");
 88 | 
 89 |   try {
 90 |     if (existsSync(docPath)) {
 91 |       return await readFile(docPath, "utf-8");
 92 |     } else {
 93 |       return `Documentation for ${component.name} not found`;
 94 |     }
 95 |   } catch (error) {
 96 |     console.error(`Error reading documentation for ${component.name}: ${error.message}`);
 97 |     return `Error reading documentation: ${error.message}`;
 98 |   }
 99 | };
100 | 
101 | // Get component API documentation
102 | const getComponentProps = async (componentName) => {
103 |   const component = await findComponentByName(componentName);
104 | 
105 |   if (!component) {
106 |     return `API documentation for component "${componentName}" not found`;
107 |   }
108 | 
109 |   try {
110 |     const apiPath = join(componentsDir, component.dirName, "api.md");
111 | 
112 |     if (existsSync(apiPath)) {
113 |       return await readFile(apiPath, "utf-8");
114 |     }
115 |     return `API documentation for ${component.name} not found`;
116 |   } catch (error) {
117 |     console.error(`Error reading API for ${component.name}: ${error.message}`);
118 |     return `Error reading API documentation: ${error.message}`;
119 |   }
120 | };
121 | 
122 | // List component examples
123 | const listComponentExamples = async (componentName) => {
124 |   const component = await findComponentByName(componentName);
125 | 
126 |   if (!component) {
127 |     return "Component not found";
128 |   }
129 | 
130 |   // First, check if we have examples.md with descriptions
131 |   const examplesMdPath = join(componentsDir, component.dirName, "examples.md");
132 | 
133 |   if (!existsSync(examplesMdPath)) {
134 |     return `No examples found for ${component.name}`;
135 |   }
136 |   try {
137 |     return await readFile(examplesMdPath, "utf-8");
138 |   } catch (error) {
139 |     console.error(`Error reading examples markdown for ${component.name}: ${error.message}`);
140 |     return `No examples found for ${component.name}`;
141 |   }
142 | };
143 | 
144 | // Get specific component example
145 | const getComponentExample = async (componentName, exampleName) => {
146 |   const component = await findComponentByName(componentName);
147 | 
148 |   if (!component) {
149 |     return `Component "${componentName}" not found`;
150 |   }
151 | 
152 |   const examplesDir = join(componentsDir, component.dirName, "examples");
153 | 
154 |   if (!existsSync(examplesDir)) {
155 |     return `No examples found for ${component.name}`;
156 |   }
157 | 
158 |   // Check for both TSX and JSX extensions
159 |   const tsxPath = join(examplesDir, `${exampleName}.tsx`);
160 |   const jsxPath = join(examplesDir, `${exampleName}.jsx`);
161 | 
162 |   const filePath = existsSync(tsxPath) ? tsxPath : existsSync(jsxPath) ? jsxPath : null;
163 | 
164 |   if (!filePath) {
165 |     return `Example "${exampleName}" not found for ${component.name}`;
166 |   }
167 | 
168 |   try {
169 |     const exampleCode = await readFile(filePath, "utf-8");
170 | 
171 |     // Try to find description for this example
172 |     let description = "";
173 |     const examplesMdPath = join(componentsDir, component.dirName, "examples.md");
174 | 
175 |     if (existsSync(examplesMdPath)) {
176 |       const examplesMd = await readFile(examplesMdPath, "utf-8");
177 |       const match = examplesMd.match(new RegExp(`- \\*\\*${exampleName}\\*\\*: (.+)$`, "m"));
178 |       if (match && match[1]) {
179 |         description = match[1].trim();
180 |       }
181 |     }
182 | 
183 |     // Add a header with description if available
184 |     if (description) {
185 |       return `// Example: ${exampleName} - ${description}\n\n${exampleCode}`;
186 |     }
187 | 
188 |     return exampleCode;
189 |   } catch (error) {
190 |     console.error(`Error reading example "${exampleName}" for ${component.name}: ${error.message}`);
191 |     return `Error reading example: ${error.message}`;
192 |   }
193 | };
194 | 
195 | // ===============================================
196 | // MCP Tools
197 | // ===============================================
198 | 
199 | // Tool: list-components
200 | server.tool("list-components", "Lists all available Ant Design components", async () => {
201 |   const components = await loadComponentsIndex();
202 |   return {
203 |     content: [
204 |       {
205 |         type: "text",
206 |         text: components.map((c) => c.name).join(", "),
207 |       },
208 |     ],
209 |   };
210 | });
211 | 
212 | // Tool: get-component-docs
213 | server.tool(
214 |   "get-component-docs",
215 |   "Gets detailed documentation for a specific component",
216 |   { componentName: z.string() },
217 |   async ({ componentName }) => {
218 |     const documentation = await getComponentDocumentation(componentName);
219 |     return {
220 |       content: [
221 |         {
222 |           type: "text",
223 |           text: documentation,
224 |         },
225 |       ],
226 |     };
227 |   },
228 | );
229 | 
230 | // Tool: get-component-props
231 | server.tool(
232 |   "get-component-props",
233 |   "Gets the props and API documentation for a specific component",
234 |   { componentName: z.string() },
235 |   async ({ componentName }) => {
236 |     const propsSection = await getComponentProps(componentName);
237 |     return {
238 |       content: [
239 |         {
240 |           type: "text",
241 |           text: propsSection,
242 |         },
243 |       ],
244 |     };
245 |   },
246 | );
247 | 
248 | // Tool: list-component-examples
249 | server.tool(
250 |   "list-component-examples",
251 |   "Lists all examples available for a specific component with descriptions",
252 |   { componentName: z.string() },
253 |   async ({ componentName }) => {
254 |     const examplesMarkdown = await listComponentExamples(componentName);
255 | 
256 |     return {
257 |       content: [
258 |         {
259 |           type: "text",
260 |           text: examplesMarkdown,
261 |         },
262 |       ],
263 |     };
264 |   },
265 | );
266 | 
267 | // Tool: get-component-example
268 | server.tool(
269 |   "get-component-example",
270 |   "Gets the code for a specific component example",
271 |   {
272 |     componentName: z.string(),
273 |     exampleName: z.string(),
274 |   },
275 |   async ({ componentName, exampleName }) => {
276 |     const exampleCode = await getComponentExample(componentName, exampleName);
277 |     return {
278 |       content: [
279 |         {
280 |           type: "text",
281 |           text: exampleCode,
282 |         },
283 |       ],
284 |     };
285 |   },
286 | );
287 | 
288 | // Tool: search-components
289 | server.tool(
290 |   "search-components",
291 |   "Search for components by name pattern",
292 |   { pattern: z.string() },
293 |   async ({ pattern }) => {
294 |     const matchingComponents = await findComponentsByPattern(pattern);
295 | 
296 |     return {
297 |       content: [
298 |         {
299 |           type: "text",
300 |           text: matchingComponents.length
301 |             ? `Matching components: ${matchingComponents.map((c) => c.name).join(", ")}`
302 |             : `No components found matching '${pattern}'`,
303 |         },
304 |       ],
305 |     };
306 |   },
307 | );
308 | 
309 | // Start the server
310 | const transport = new StdioServerTransport();
311 | await server.connect(transport);
312 | 
```