UNPKG

component-genie-mcp

Version:

A simplified Model Context Protocol server for querying React components with complete content access

895 lines (783 loc) 28.6 kB
#!/usr/bin/env node import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import fs from "fs/promises"; import { z } from "zod"; import path from "path"; import { fileURLToPath } from "url"; // 获取当前模块的目录路径 const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // 组件信息已内置,无需动态获取项目根目录 // 使用功能更完整的McpServer const server = new McpServer( { name: "component-genie-mcp", version: "1.0.0", description: "ComponentGenie MCP服务器 - 提供内置组件的查询和详情获取", }, { capabilities: { tools: {}, }, } ); // 1. 获取组件列表工具 server.tool( "get_component_list", { search: z.string().optional(), limit: z.number().optional(), }, async (params) => { // TODO: 修复参数传递问题 // 当前 MCP SDK 可能存在参数传递的问题,暂时使用默认值 // 在实际使用时,AI 客户端(如 Claude)会正确传递参数 const { search, limit = 50 } = params; try { // 尝试多个可能的文件路径 const possiblePaths = [ path.join(__dirname, "resources/components-detail.json"), path.join(process.cwd(), "src/resources/components-detail.json"), path.join(__dirname, "../src/resources/components-detail.json"), path.join(process.cwd(), "resources/components-detail.json"), ]; let componentsPath = null; let fileContent = null; // 尝试找到存在的文件 for (const tryPath of possiblePaths) { try { await fs.access(tryPath); componentsPath = tryPath; fileContent = await fs.readFile(tryPath, "utf-8"); break; } catch (error) { // 继续尝试下一个路径 continue; } } if (!fileContent) { throw new Error( `找不到组件信息文件,已尝试以下路径:\n${possiblePaths.join("\n")}` ); } // 在第一次成功加载时记录路径 if (!server._componentsPath) { server._componentsPath = componentsPath; console.error(`📁 成功加载组件信息文件: ${componentsPath}`); } const componentsData = JSON.parse(fileContent); let { components, totalCount, timestamp } = componentsData; // 如果有搜索关键词,进行过滤 if (search) { const searchLower = search.toLowerCase(); components = components.filter( (comp) => comp.name.toLowerCase().includes(searchLower) || comp?.whenToUse?.toLowerCase().includes(searchLower) ); } // 限制返回数量 if (limit && limit > 0 && components.length > limit) { components = components.slice(0, limit); } return { content: [ { type: "text", text: `找到 ${components.length} 个组件${ search ? ` (搜索: "${search}")` : "" }:\n${components .map( (comp) => `- ${comp.name}: ${comp?.whenToUse?.substring(0, 100)}${ comp?.whenToUse?.length > 100 ? "..." : "" }` ) .join("\n")}`, }, ], data: { componentList: components, componentDetails: components, totalCount: search ? components.length : totalCount, timestamp: timestamp, searchKeyword: search || null, }, }; } catch (error) { return { content: [ { type: "text", text: `获取组件列表失败: ${error.message}`, }, ], }; } } ); // 2. 获取组件详情工具 server.tool( "get_component_details", { componentName: z .array(z.string()) .describe("要获取详情的组件名称数组,支持批量查询"), }, async (params) => { const { componentName: rawComponentName } = params; // 简化后的固定参数:总是包含所有内容 const includeImplementation = true; const maxContentLength = Number.MAX_SAFE_INTEGER; // 不限制内容长度 const maxDemoCount = Number.MAX_SAFE_INTEGER; // 不限制demo数量 // 处理组件名称参数,支持数组或序列化的数组字符串 let componentNames; if (Array.isArray(rawComponentName)) { componentNames = rawComponentName; } else if (typeof rawComponentName === "string") { // 尝试解析字符串化的数组 if (rawComponentName.startsWith("[") && rawComponentName.endsWith("]")) { try { const parsed = JSON.parse(rawComponentName); if (Array.isArray(parsed)) { componentNames = parsed; } else { componentNames = [rawComponentName]; } } catch (error) { // 解析失败,将字符串作为单个组件名处理 componentNames = [rawComponentName]; } } else { // 单个组件名 componentNames = [rawComponentName]; } } else { throw new Error("componentName 必须是字符串或字符串数组"); } // 验证解析后的组件名称 if ( !Array.isArray(componentNames) || !componentNames.every((name) => typeof name === "string") ) { throw new Error("componentName 必须包含有效的字符串组件名"); } if (componentNames.length === 0) { throw new Error("至少需要提供一个组件名称"); } // 内容截取函数(简化后不再截取) function truncateContent(content, maxLength = maxContentLength) { if (!content) { return { content, truncated: false }; } // 简化后总是返回完整内容 return { content: content, truncated: false, originalLength: content.length, }; } // 获取文件摘要信息 function getFileSummary(content) { if (!content) return null; const lines = content.split("\n"); const summary = { totalLines: lines.length, imports: lines.filter((line) => line.trim().startsWith("import")) .length, exports: lines.filter((line) => line.trim().startsWith("export")) .length, functions: (content.match(/function\s+\w+|const\s+\w+\s*=\s*\(/g) || []) .length, components: (content.match(/const\s+\w+\s*=\s*\(/g) || []).length, }; return summary; } try { // 从components-detail.json中获取组件信息 const possiblePaths = [ path.join(__dirname, "resources/components-detail.json"), path.join(process.cwd(), "src/resources/components-detail.json"), path.join(__dirname, "../src/resources/components-detail.json"), path.join(process.cwd(), "resources/components-detail.json"), ]; let componentsPath = null; let fileContent = null; // 尝试找到存在的文件 for (const tryPath of possiblePaths) { try { await fs.access(tryPath); componentsPath = tryPath; fileContent = await fs.readFile(tryPath, "utf-8"); break; } catch (error) { // 继续尝试下一个路径 continue; } } if (!fileContent) { throw new Error( `找不到组件详情文件,已尝试以下路径:\n${possiblePaths.join("\n")}` ); } const componentsData = JSON.parse(fileContent); // 批量处理多个组件 const allComponentDetails = []; const notFoundComponents = []; // 用于处理单个组件的函数 const processComponent = (singleComponentName, componentInfo) => { // 从JSON中提取组件信息 let componentDefinition = null; let componentDefinitionTruncated = false; let componentDefinitionOriginalLength = 0; let definitionExists = false; if (componentInfo.typeDefinition) { definitionExists = true; const { content, truncated, originalLength } = truncateContent( componentInfo.typeDefinition ); componentDefinition = content; componentDefinitionTruncated = truncated; componentDefinitionOriginalLength = originalLength; } // 处理组件实现代码 let componentCode = null; let componentCodeTruncated = false; let componentCodeOriginalLength = 0; let componentExists = false; if (componentInfo.componentCode) { componentExists = true; const { content, truncated, originalLength } = truncateContent( componentInfo.componentCode ); componentCode = content; componentCodeTruncated = truncated; componentCodeOriginalLength = originalLength; } // 处理README文档 let componentReadme = null; let componentReadmeTruncated = false; let componentReadmeOriginalLength = 0; let readmeExists = false; if (componentInfo.documentation) { readmeExists = true; const { content, truncated, originalLength } = truncateContent( componentInfo.documentation ); componentReadme = content; componentReadmeTruncated = truncated; componentReadmeOriginalLength = originalLength; } // 处理demo文件 let componentDemos = []; let demosExist = false; if (componentInfo.demos && componentInfo.demos.length > 0) { demosExist = true; // 简化后返回所有demo,不进行数量限制 for (const demo of componentInfo.demos) { const { content, truncated, originalLength } = truncateContent( demo.content ); const summary = getFileSummary(demo.content); componentDemos.push({ filename: demo.name, path: `docs/src/${singleComponentName}/demo/${demo.name}`, // 生成相对路径 content: content, truncated: truncated, originalLength: originalLength, summary: summary, }); } } // 判断组件是否存在 const hasAnyFiles = componentExists || definitionExists || readmeExists || demosExist; // 构建单个组件的详情数据 return { componentName: singleComponentName, found: hasAnyFiles, contentStatus: { typeDefinition: definitionExists, implementation: componentExists, documentation: readmeExists, demos: demosExist, }, contents: { typeDefinition: definitionExists ? { content: componentDefinition, truncated: componentDefinitionTruncated, originalLength: componentDefinitionOriginalLength, } : null, implementation: componentExists ? { content: componentCode, truncated: componentCodeTruncated, originalLength: componentCodeOriginalLength, } : null, documentation: readmeExists ? { content: componentReadme, truncated: componentReadmeTruncated, originalLength: componentReadmeOriginalLength, } : null, demos: componentDemos, }, whenToUse: componentInfo?.whenToUse || null, }; }; // 遍历处理每个组件 for (const singleComponentName of componentNames) { const componentInfo = componentsData.components.find( (comp) => comp.name === singleComponentName ); if (!componentInfo) { notFoundComponents.push(singleComponentName); continue; } const componentDetail = processComponent( singleComponentName, componentInfo ); allComponentDetails.push(componentDetail); } // 构建响应文本 let responseText = ""; // 如果只查询一个组件 if (componentNames.length === 1) { const singleComponentName = componentNames[0]; const componentDetail = allComponentDetails.find( (detail) => detail.componentName === singleComponentName ); if (componentDetail) { // 找到组件,显示详细信息 responseText = buildSingleComponentResponse(componentDetail, true); } else { // 未找到组件 responseText = `# ${singleComponentName} 组件详情\n\n`; responseText += `⚠️ 未找到组件 "${singleComponentName}" 的信息。\n\n`; } } else { // 批量查询多个组件 responseText = `# 组件查询结果\n\n`; if (allComponentDetails.length > 0) { responseText += `## 找到的组件 (${allComponentDetails.length}个)\n\n`; // 遍历显示每个组件的详情 allComponentDetails.forEach((componentDetail, index) => { responseText += `### ${index + 1}. ${ componentDetail.componentName }\n\n`; responseText += buildComponentSummary(componentDetail, true); responseText += `\n---\n\n`; }); } if (notFoundComponents.length > 0) { responseText += `## 未找到的组件 (${notFoundComponents.length}个)\n\n`; notFoundComponents.forEach((compName, index) => { responseText += `### ${index + 1}. ${compName}\n`; responseText += `⚠️ 未找到该组件的相关信息。\n\n`; }); } } // 构建返回数据 const responseData = { requestedComponents: componentNames, foundComponents: allComponentDetails.map( (detail) => detail.componentName ), notFoundComponents: notFoundComponents, totalRequested: componentNames.length, totalFound: allComponentDetails.length, includeImplementation: true, // 简化后总是为true maxContentLength: "unlimited", // 简化后不限制 maxDemoCount: "unlimited", // 简化后不限制 componentDetails: allComponentDetails, }; // 在 content 中直接包含真正的组件详情内容 let structuredInfo = ` --- ## 📊 组件详情数据 ### 查询概览 - **请求组件数**: ${responseData.totalRequested} - **找到组件数**: ${responseData.totalFound} - **未找到组件**: ${ responseData.notFoundComponents.length > 0 ? responseData.notFoundComponents.join(", ") : "无" } - **内容策略**: 包含所有可用内容,无长度限制 ### 组件详细信息`; // 为每个找到的组件添加详细信息 responseData.componentDetails.forEach((detail, index) => { structuredInfo += ` #### ${index + 1}. ${detail.componentName} **基本信息**: - 组件名称: \`${detail.componentName}\` - 使用场景: ${detail.whenToUse || "未提供"} **内容状态**: - 类型定义: ${detail.contentStatus.typeDefinition ? "✅ 存在" : "❌ 不存在"} - 实现代码: ${detail.contentStatus.implementation ? "✅ 存在" : "❌ 不存在"} - 组件文档: ${detail.contentStatus.documentation ? "✅ 存在" : "❌ 不存在"} - Demo示例: ${detail.contentStatus.demos ? "✅ 存在" : "❌ 不存在"}`; // 添加类型定义内容 if (detail.contents.typeDefinition?.content) { structuredInfo += ` **类型定义内容** (${detail.contents.typeDefinition.originalLength} 字符): \`\`\`typescript ${detail.contents.typeDefinition.content} \`\`\``; } // 添加实现代码内容 if (detail.contents.implementation?.content) { structuredInfo += ` **实现代码内容** (${detail.contents.implementation.originalLength} 字符): \`\`\`typescript ${detail.contents.implementation.content} \`\`\``; } // 添加文档内容 if (detail.contents.documentation?.content) { structuredInfo += ` **文档内容** (${detail.contents.documentation.originalLength} 字符): ${detail.contents.documentation.content}`; } // 添加Demo示例内容 if (detail.contents.demos && detail.contents.demos.length > 0) { structuredInfo += ` **Demo示例** (${detail.contents.demos.length} 个文件):`; detail.contents.demos.forEach((demo, demoIndex) => { if (demo.content) { const fileExt = getFileExtension(demo.filename); structuredInfo += ` ##### Demo ${demoIndex + 1}: ${demo.filename} 文件信息: ${demo.summary?.totalLines || 0} 行,${demo.summary?.functions || 0} 个函数 \`\`\`${fileExt} ${demo.content} \`\`\``; } }); } }); return { content: [ { type: "text", text: responseText + structuredInfo, }, ], data: responseData, }; } catch (error) { return { content: [ { type: "text", text: `获取组件详情失败: ${error.message}`, }, ], data: { error: error.message, found: false, componentNames, }, }; } } ); // 3. 根据页面场景推荐组件工具 // server.tool( // "get_components_by_scenario", // { // scenario: z // .enum(["列表页", "表单页"]) // .describe("页面场景描述,例如:列表页"), // }, // async (params) => { // const { scenario } = params; // try { // // 场景与组件的映射规则 // const scenarioMappings = { // // 数据表格相关场景 // 列表页: { // /** 相关组件 */ // components: [ // "AutoTable", // "DPHeader", // "ActionGroup", // "Abbr", // "BlueInfoTip", // ], // /** 参考页面路径 */ // reference: [ // "src/pages/Market/CatalogManage/DataManager/TaskList/index.tsx", // "src/pages/Market/CatalogManage/DataManager/TaskList/Header.tsx", // "src/pages/Market/CatalogManage/DataManager/TaskList/TaskList.tsx", // "src/pages/Market/CatalogManage/DataManager/TaskList/slice.ts", // ], // }, // // 表单页 // 表单页: { // components: [ // "Form", // "FormItem", // "Button", // "Radio", // "Checkbox", // "DPSingleSelect", // "DPMultiSelect", // ], // reference: [ // "src/pages/Market/CatalogManage/Components/CreateTaskModal/index.tsx", // ], // }, // }; // // 根据场景关键词匹配组件 // let recommendedComponents = []; // let referenceFiles = []; // const scenarioLower = scenario.toLowerCase(); // // 精确匹配 // for (const [key, config] of Object.entries(scenarioMappings)) { // if (scenarioLower.includes(key.toLowerCase()) || scenario === key) { // recommendedComponents = [ // ...new Set([...recommendedComponents, ...config.components]), // ]; // referenceFiles = [ // ...new Set([...referenceFiles, ...config.reference]), // ]; // } // } // // 获取组件详细信息 // let componentsPath = null; // let fileContent = null; // const possiblePaths = [ // path.join(__dirname, "resources/components-detail.json"), // path.join(process.cwd(), "src/resources/components-detail.json"), // path.join(__dirname, "../src/resources/components-detail.json"), // path.join(process.cwd(), "resources/components-detail.json"), // ]; // for (const tryPath of possiblePaths) { // try { // await fs.access(tryPath); // componentsPath = tryPath; // fileContent = await fs.readFile(tryPath, "utf-8"); // break; // } catch (error) { // continue; // } // } // let componentDetails = []; // if (fileContent) { // const componentsData = JSON.parse(fileContent); // componentDetails = componentsData.components.filter((comp) => // recommendedComponents.includes(comp.name) // ); // } // // 构建响应文本 // let responseText = `# 页面场景推荐 - ${scenario}\n\n`; // if (referenceFiles.length > 0) { // responseText += `## 参考文件路径\n\n`; // referenceFiles.forEach((filePath, index) => { // responseText += `### ${index + 1}. ${filePath}\n`; // responseText += `**文件路径**: \`${filePath}\`\n\n`; // }); // } // if (componentDetails.length > 0) { // responseText += `## 推荐组件 (${componentDetails.length}个)\n\n`; // componentDetails.forEach((comp, index) => { // responseText += `### ${index + 1}. ${comp.name}\n`; // responseText += `**使用场景**: ${comp.whenToUse}\n`; // responseText += `**类型定义**: ${comp.typeDefinition ? '✅ 可用' : '❌ 无'}\n`; // responseText += `**文档**: ${comp.documentation ? '✅ 可用' : '❌ 无'}\n`; // responseText += `**示例**: ${comp.demos && comp.demos.length > 0 ? `✅ ${comp.demos.length}个` : '❌ 无'}\n\n`; // }); // } else { // responseText += `## 推荐组件\n\n`; // recommendedComponents.forEach((compName, index) => { // responseText += `### ${index + 1}. ${compName}\n`; // responseText += `💡 *使用 \`get_component_details\` 工具获取该组件的详细信息*\n\n`; // }); // } // responseText += `## 使用建议\n\n`; // responseText += `1. 优先查看参考文件,了解页面结构和组件使用方式\n`; // responseText += `2. 然后查看推荐组件的文档和示例\n`; // responseText += `3. 根据具体需求调整组件配置\n`; // responseText += `4. 必要时使用 \`get_component_details\` 工具获取组件详细信息\n`; // return { // content: [ // { // type: "text", // text: responseText, // }, // ], // data: { // scenario, // recommendedComponents, // componentDetails, // referenceFiles, // projectRoot: PROJECT_ROOT, // suggestions: { // primaryComponents: recommendedComponents.slice(0, 3), // secondaryComponents: recommendedComponents.slice(3), // referenceFiles: referenceFiles, // }, // }, // }; // } catch (error) { // return { // content: [ // { // type: "text", // text: `根据场景推荐组件失败: ${error.message}`, // }, // ], // data: { // error: error.message, // scenario, // }, // }; // } // } // ); // 构建单个组件响应文本的辅助函数 function buildSingleComponentResponse(componentDetail, includeImplementation) { const { componentName, found, contentStatus, contents } = componentDetail; let responseText = `# ${componentName} 组件详情\n\n`; if (!found) { responseText += `⚠️ 未找到组件 "${componentName}" 的信息。\n\n`; } else { // 添加文件状态概览 responseText += `## 文件状态\n\n`; responseText += `- 组件实现: ${ contentStatus.implementation ? "✅ 存在" : "❌ 不存在" }\n`; responseText += `- 组件定义: ${ contentStatus.typeDefinition ? "✅ 存在" : "❌ 不存在" }\n`; responseText += `- 组件文档: ${ contentStatus.documentation ? "✅ 存在" : "❌ 不存在" }\n`; responseText += `- 使用示例: ${ contentStatus.demos ? "✅ 存在" : "❌ 不存在" }\n\n`; // 添加组件定义信息 if (contents.typeDefinition?.content) { responseText += `## 组件类型定义\n\n`; responseText += `\`\`\`typescript\n${contents.typeDefinition.content}\n\`\`\`\n\n`; } // 添加组件实现代码 if (contents.implementation?.content) { responseText += `## 组件实现\n\n`; responseText += `\`\`\`typescript\n${contents.implementation.content}\n\`\`\`\n\n`; } // 添加README文档 if (contents.documentation?.content) { responseText += `## 组件文档\n\n`; responseText += contents.documentation.content + "\n\n"; } // 添加Demo示例 if (contents.demos && contents.demos.length > 0) { responseText += `## 使用示例\n\n`; contents.demos.forEach((demo, index) => { if (demo.isMoreIndicator) { responseText += `### ${demo.filename}\n\n`; return; } responseText += `### 示例 ${index + 1}: ${demo.filename}\n\n`; // 添加文件摘要信息 if (demo.summary) { responseText += `**文件信息**: ${demo.summary.totalLines} 行,${demo.summary.functions} 个函数,${demo.summary.components} 个组件\n\n`; } if (demo.content) { responseText += `\`\`\`${getFileExtension(demo.filename)}\n${ demo.content }\n\`\`\`\n\n`; } }); } } return responseText; } // 构建组件摘要的辅助函数 function buildComponentSummary(componentDetail, includeImplementation) { const { componentName, found, contentStatus, contents, whenToUse } = componentDetail; let summaryText = ""; if (!found) { summaryText += `⚠️ 未找到该组件的相关文件\n\n`; return summaryText; } // 添加使用场景 if (whenToUse) { summaryText += `**使用场景**: ${whenToUse}\n\n`; } // 添加文件状态 summaryText += `**文件状态**:\n`; summaryText += `- 组件实现: ${contentStatus.implementation ? "✅" : "❌"}\n`; summaryText += `- 组件定义: ${contentStatus.typeDefinition ? "✅" : "❌"}\n`; summaryText += `- 组件文档: ${contentStatus.documentation ? "✅" : "❌"}\n`; summaryText += `- 使用示例: ${ contentStatus.demos ? `✅ (${contents.demos?.length || 0}个)` : "❌" }\n\n`; // 显示简要代码信息 if (contents.typeDefinition?.content) { const lines = contents.typeDefinition.content.split("\n").length; summaryText += `**类型定义**: ${lines} 行\n`; } if (contents.implementation?.content) { const lines = contents.implementation.content.split("\n").length; summaryText += `**组件实现**: ${lines} 行\n`; } if (contents.documentation?.content) { const lines = contents.documentation.content.split("\n").length; summaryText += `**文档内容**: ${lines} 行\n`; } summaryText += `\n`; return summaryText; } // 工具函数:获取文件扩展名用于代码高亮 function getFileExtension(filePath) { if (!filePath) return "text"; const ext = path.extname(filePath).slice(1).toLowerCase(); switch (ext) { case "tsx": case "ts": return "typescript"; case "jsx": case "js": return "javascript"; case "css": return "css"; case "scss": return "scss"; case "json": return "json"; case "md": return "markdown"; case "html": return "html"; case "xml": return "xml"; default: return ext || "text"; } } // 启动服务器 async function main() { const transport = new StdioServerTransport(); await server.connect(transport); console.error("🚀 ComponentGenie MCP Server started on stdio"); console.error( "📋 Available tools: get_component_list, get_component_details (支持批量查询)" ); console.error( "📁 Components data file: src/resources/components-detail.json" ); } main().catch((error) => { console.error("Failed to start MCPServer:", error); process.exit(1); });