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
JavaScript
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);
});