Ant Design Components MCP Server

by hannesj
Verified
#!/usr/bin/env node import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { Console } from "node:console"; import { existsSync } from "node:fs"; import { readFile } from "node:fs/promises"; import { dirname, join, resolve } from "node:path"; import { fileURLToPath } from "node:url"; import { z } from "zod"; // Get the directory of the current script const __dirname = dirname(fileURLToPath(import.meta.url)); globalThis.console = new Console(process.stderr); // Path to the pre-extracted data const dataDir = resolve(__dirname, "data"); const componentsDir = join(dataDir, "components"); // Check if the data directory exists if (!existsSync(dataDir) || !existsSync(componentsDir)) { console.error(`Error: Data directory not found at ${dataDir}`); console.error("Please run the extraction script first:"); console.error(" node scripts/extract-docs.mjs [path/to/ant-design]"); process.exit(1); } // Check if the components index exists const componentsIndexPath = join(dataDir, "components-index.json"); if (!existsSync(componentsIndexPath)) { console.error(`Error: Components index not found at ${componentsIndexPath}`); console.error("Please run the extraction script first."); process.exit(1); } // Initialize the MCP server const server = new McpServer({ name: "Ant Design Components", version: "1.0.0", description: "Provides documentation and examples for Ant Design components", }); // =============================================== // Utility functions // =============================================== // Load components index from the extracted data async function loadComponentsIndex() { try { const indexData = await readFile(componentsIndexPath, "utf-8"); return JSON.parse(indexData); } catch (error) { console.error(`Error loading components index: ${error.message}`); return []; } } // Find a component by name (case-insensitive) async function findComponentByName(componentName) { const components = await loadComponentsIndex(); return components.find( (c) => c.name.toLowerCase() === componentName.toLowerCase() || c.dirName.toLowerCase() === componentName.toLowerCase(), ); } // Find components matching a pattern async function findComponentsByPattern(pattern) { const components = await loadComponentsIndex(); const regexPattern = new RegExp(pattern, "i"); return components.filter((c) => regexPattern.test(c.name) || regexPattern.test(c.dirName)); } // =============================================== // Data fetching functions // =============================================== // Get component markdown documentation const getComponentDocumentation = async (componentName) => { const component = await findComponentByName(componentName); if (!component) { return `Documentation for component "${componentName}" not found`; } const docPath = join(componentsDir, component.dirName, "docs.md"); try { if (existsSync(docPath)) { return await readFile(docPath, "utf-8"); } else { return `Documentation for ${component.name} not found`; } } catch (error) { console.error(`Error reading documentation for ${component.name}: ${error.message}`); return `Error reading documentation: ${error.message}`; } }; // Get component API documentation const getComponentProps = async (componentName) => { const component = await findComponentByName(componentName); if (!component) { return `API documentation for component "${componentName}" not found`; } try { const apiPath = join(componentsDir, component.dirName, "api.md"); if (existsSync(apiPath)) { return await readFile(apiPath, "utf-8"); } return `API documentation for ${component.name} not found`; } catch (error) { console.error(`Error reading API for ${component.name}: ${error.message}`); return `Error reading API documentation: ${error.message}`; } }; // List component examples const listComponentExamples = async (componentName) => { const component = await findComponentByName(componentName); if (!component) { return "Component not found"; } // First, check if we have examples.md with descriptions const examplesMdPath = join(componentsDir, component.dirName, "examples.md"); if (!existsSync(examplesMdPath)) { return `No examples found for ${component.name}`; } try { return await readFile(examplesMdPath, "utf-8"); } catch (error) { console.error(`Error reading examples markdown for ${component.name}: ${error.message}`); return `No examples found for ${component.name}`; } }; // Get specific component example const getComponentExample = async (componentName, exampleName) => { const component = await findComponentByName(componentName); if (!component) { return `Component "${componentName}" not found`; } const examplesDir = join(componentsDir, component.dirName, "examples"); if (!existsSync(examplesDir)) { return `No examples found for ${component.name}`; } // Check for both TSX and JSX extensions const tsxPath = join(examplesDir, `${exampleName}.tsx`); const jsxPath = join(examplesDir, `${exampleName}.jsx`); const filePath = existsSync(tsxPath) ? tsxPath : existsSync(jsxPath) ? jsxPath : null; if (!filePath) { return `Example "${exampleName}" not found for ${component.name}`; } try { const exampleCode = await readFile(filePath, "utf-8"); // Try to find description for this example let description = ""; const examplesMdPath = join(componentsDir, component.dirName, "examples.md"); if (existsSync(examplesMdPath)) { const examplesMd = await readFile(examplesMdPath, "utf-8"); const match = examplesMd.match(new RegExp(`- \\*\\*${exampleName}\\*\\*: (.+)$`, "m")); if (match && match[1]) { description = match[1].trim(); } } // Add a header with description if available if (description) { return `// Example: ${exampleName} - ${description}\n\n${exampleCode}`; } return exampleCode; } catch (error) { console.error(`Error reading example "${exampleName}" for ${component.name}: ${error.message}`); return `Error reading example: ${error.message}`; } }; // =============================================== // MCP Tools // =============================================== // Tool: list-components server.tool("list-components", "Lists all available Ant Design components", async () => { const components = await loadComponentsIndex(); return { content: [ { type: "text", text: components.map((c) => c.name).join(", "), }, ], }; }); // Tool: get-component-docs server.tool( "get-component-docs", "Gets detailed documentation for a specific component", { componentName: z.string() }, async ({ componentName }) => { const documentation = await getComponentDocumentation(componentName); return { content: [ { type: "text", text: documentation, }, ], }; }, ); // Tool: get-component-props server.tool( "get-component-props", "Gets the props and API documentation for a specific component", { componentName: z.string() }, async ({ componentName }) => { const propsSection = await getComponentProps(componentName); return { content: [ { type: "text", text: propsSection, }, ], }; }, ); // Tool: list-component-examples server.tool( "list-component-examples", "Lists all examples available for a specific component with descriptions", { componentName: z.string() }, async ({ componentName }) => { const examplesMarkdown = await listComponentExamples(componentName); return { content: [ { type: "text", text: examplesMarkdown, }, ], }; }, ); // Tool: get-component-example server.tool( "get-component-example", "Gets the code for a specific component example", { componentName: z.string(), exampleName: z.string(), }, async ({ componentName, exampleName }) => { const exampleCode = await getComponentExample(componentName, exampleName); return { content: [ { type: "text", text: exampleCode, }, ], }; }, ); // Tool: search-components server.tool( "search-components", "Search for components by name pattern", { pattern: z.string() }, async ({ pattern }) => { const matchingComponents = await findComponentsByPattern(pattern); return { content: [ { type: "text", text: matchingComponents.length ? `Matching components: ${matchingComponents.map((c) => c.name).join(", ")}` : `No components found matching '${pattern}'`, }, ], }; }, ); // Start the server const transport = new StdioServerTransport(); await server.connect(transport);