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