openEuler-portal-mcp开发者指南:如何扩展自定义查询工具
openEuler-portal-mcp开发者指南如何扩展自定义查询工具【免费下载链接】openEuler-portal-mcpThe repository of openEuler portal MCP Server项目地址: https://gitcode.com/openeuler/openEuler-portal-mcp前往项目官网免费下载https://ar.openeuler.org/ar/openEuler-portal-mcp是一个基于Model Context ProtocolMCP的开源项目为Claude等AI工具提供openEuler官网相关信息的查询能力。本文将详细介绍如何扩展自定义查询工具帮助开发者快速上手并为项目添加新的功能模块。 项目架构概述openEuler-portal-mcp采用分层架构设计包含以下几个核心模块MCP协议层使用modelcontextprotocol/sdk实现标准MCP协议传输层支持Stdio本地连接和SSE远程连接两种模式核心层工具注册中心和请求路由分发工具层包含21个查询工具和操作工具服务层提供共享服务如文档版本缓存辅助层格式化工具和智能推荐系统️ 现有工具概览项目目前提供21个工具分为两大类查询类工具19个get_sig_infoSIG信息查询get_cve_infoCVE安全公告查询get_docs_info文档内容检索get_package_info软件包信息查询get_issue_info社区Issue查询get_pull_request_info社区PR查询操作类工具2个execute_user_operation用户操作执行需OPENEULER_TOKENexecute_forum_operation论坛用户操作执行需FORUM_TOKEN 创建新工具的完整步骤步骤1创建工具文件在src/tools/目录下创建新的工具文件例如src/tools/getMyCustomTool.jsimport { addField, addFields, addIndentedField, addIndentedFields, truncate, stripHtml } from ../utils/formatHelpers.js; import { appendRecommendation } from ../utils/toolRecommendations.js; // 数据源URL定义 const MY_API_URL https://api.example.com/data; // 缓存机制可选 let cachedData null; let cacheExpiry 0; const CACHE_DURATION 15 * 60 * 1000; // 15分钟 // 主工具函数 export async function getMyCustomTool(params) { const { query_type list, keyword } params; try { let result ; // 根据查询类型处理不同逻辑 switch (query_type) { case list: result await fetchListData(keyword); break; case detail: result await fetchDetailData(keyword); break; default: return 请指定有效的查询类型list列表或 detail详情; } // 添加智能推荐 result appendRecommendation(result, get_my_custom_tool); return result; } catch (error) { console.error(自定义工具执行错误:, error); return 查询失败: ${error.message}; } } // 辅助函数获取列表数据 async function fetchListData(keyword) { const now Date.now(); // 检查缓存 if (cachedData now cacheExpiry) { return formatListData(cachedData); } // 发起API请求 const response await fetch(MY_API_URL, { signal: AbortSignal.timeout(15000) // 15秒超时 }); if (!response.ok) { throw new Error(API请求失败: ${response.status}); } const data await response.json(); // 更新缓存 cachedData data; cacheExpiry now CACHE_DURATION; return formatListData(data); } // 辅助函数格式化列表数据 function formatListData(data) { let output ## 自定义数据列表\n\n; if (data.length 0) { return 未找到相关数据。; } data.forEach((item, index) { output ### ${index 1}. ${item.name}\n; output addField(描述, item.description || 暂无描述); output addField(状态, item.status || 未知); output addField(更新时间, item.updated_at || 未知); output \n; }); return output; } // 工具定义必须导出 export const toolDefinition { name: get_my_custom_tool, description: 查询自定义数据信息支持列表和详情查询, inputSchema: { type: object, properties: { query_type: { type: string, description: 查询类型list列表查询或 detail详情查询, enum: [list, detail], default: list }, keyword: { type: string, description: 搜索关键词可选 } } } };步骤2注册工具到主入口在src/index.js中添加新工具的导入和注册// 在导入部分添加 import { getMyCustomTool, toolDefinition as getMyCustomToolDef } from ./tools/getMyCustomTool.js; // 在工具注册部分添加 const tools [ // ... 现有工具 getMyCustomToolDef, ]; const toolHandlers { // ... 现有处理器 get_my_custom_tool: getMyCustomTool, };步骤3配置智能推荐在src/utils/toolRecommendations.js中添加新工具的推荐配置// 在toolRecommendations对象中添加 const toolRecommendations { // ... 现有配置 get_my_custom_tool: { related: [get_sig_info, get_organization_info, get_search_info], scenarios: [ 用户想查询自定义数据时, 需要了解特定信息时, 搜索相关数据时 ] }, // 在其他工具的推荐中添加新工具 get_sig_info: { related: [get_my_custom_tool, get_organization_info, get_meeting_info, get_development_info], // ... } };步骤4编写单元测试在tests/目录下创建测试文件getMyCustomTool.test.jsimport { describe, it, expect, beforeEach, afterEach, vi } from vitest; import { getMyCustomTool } from ../src/tools/getMyCustomTool.js; describe(getMyCustomTool, () { beforeEach(() { vi.useFakeTimers(); }); afterEach(() { vi.useRealTimers(); }); it(应该返回列表数据, async () { const result await getMyCustomTool({ query_type: list }); expect(result).toContain(自定义数据列表); }); it(应该处理API错误, async () { global.fetch vi.fn(() Promise.reject(new Error(网络错误))); const result await getMyCustomTool({ query_type: list }); expect(result).toContain(查询失败); }); }); 工具开发最佳实践1. 缓存策略设计项目采用三级缓存机制建议根据数据特性选择合适的缓存策略// 共享缓存跨工具使用 // 位置src/services/docsVersionService.js // 适用场景多个工具共享的数据如文档版本信息 // 本地缓存工具内部使用 // 适用场景单个工具专用的数据 const CACHE_DURATION 15 * 60 * 1000; // 15分钟 let cachedData null; let cacheExpiry 0; // 长期缓存24小时 // 适用场景用户信息等变化频率低的数据 const LONG_CACHE_DURATION 24 * 60 * 60 * 1000;2. 错误处理规范遵循统一的错误处理模式try { // 业务逻辑 const response await fetch(url, { signal: AbortSignal.timeout(15000) // 15秒超时 }); if (!response.ok) { throw new Error(API请求失败: ${response.status}); } // 数据处理 const data await response.json(); return formatData(data); } catch (error) { console.error(工具执行错误:, error); // 返回用户友好的错误信息 if (error.name TimeoutError) { return 请求超时请稍后重试。; } return 查询失败: ${error.message}; }3. 输出格式化标准使用项目提供的格式化工具确保输出一致性import { addField, addFields, addIndentedField, addIndentedFields, truncate, stripHtml } from ../utils/formatHelpers.js; function formatData(data) { let output ## 数据标题\n\n; // 添加字段 output addField(名称, data.name); output addField(描述, data.description); // 添加多行字段 output addIndentedField(详细信息, data.details); // 处理HTML内容 const cleanContent stripHtml(data.content); return output; }4. 智能推荐集成每个工具都应集成智能推荐系统import { appendRecommendation } from ../utils/toolRecommendations.js; async function myTool(params) { // ... 业务逻辑 let result formatData(data); // 添加推荐 result appendRecommendation(result, my_tool_name); return result; } 项目文件结构详解了解项目结构有助于更好地扩展功能openEuler-portal-mcp/ ├── src/ │ ├── index.js # 主入口文件工具注册中心 │ ├── services/ │ │ └── docsVersionService.js # 共享服务文档版本缓存 │ ├── tools/ # 工具函数目录21个工具 │ │ ├── getSigInfo.js # SIG信息查询 │ │ ├── getCveInfo.js # CVE安全公告查询 │ │ ├── getDocsInfo.js # 文档内容检索 │ │ ├── getDocsSearchContent.js # 文档内容搜索 │ │ ├── getPackageInfo.js # 软件包信息查询 │ │ ├── getIssueInfo.js # 社区Issue查询 │ │ ├── getPullRequestInfo.js # 社区PR查询 │ │ ├── executeUserOperation.js # 用户操作执行 │ │ └── executeForumOperation.js # 论坛用户操作执行 │ └── utils/ │ ├── formatHelpers.js # 输出格式化辅助函数 │ └── toolRecommendations.js # 智能推荐系统配置 ├── tests/ # 测试文件目录 │ ├── getSigInfo.test.js # SIG查询测试 │ ├── getCveInfo.test.js # CVE查询测试 │ └── ... # 其他工具测试 ├── docs/ │ ├── ARCHITECTURE.md # 项目架构文档 │ └── TOOL_SELECTION.md # 工具选择说明 └── package.json # 项目配置 工具间的协作模式1. 数据共享机制通过服务层实现工具间的数据共享// 在services目录下创建共享服务 // src/services/mySharedService.js export class MySharedService { static cache new Map(); static async getSharedData() { const cacheKey shared_data; const cached this.cache.get(cacheKey); if (cached Date.now() cached.expiry) { return cached.data; } // 获取数据 const data await fetchData(); // 更新缓存15分钟 this.cache.set(cacheKey, { data, expiry: Date.now() 15 * 60 * 1000 }); return data; } } // 在工具中使用共享服务 import { MySharedService } from ../services/mySharedService.js; async function myTool() { const sharedData await MySharedService.getSharedData(); // 使用共享数据 }2. 推荐链设计工具之间形成推荐链引导用户深入查询// 在toolRecommendations.js中配置推荐关系 const toolRecommendations { get_sig_info: { related: [get_meeting_info, get_organization_info, get_development_info], scenarios: [ 查询SIG信息后可能需要查看该SIG的会议安排, 了解SIG后可能需要查看社区组织架构, 对SIG感兴趣可能需要查看相关开发活动 ] }, get_meeting_info: { related: [get_sig_info, execute_user_operation, get_forum_info], scenarios: [ 查看会议后可能需要了解相关SIG信息, 如需参加会议可能需要执行用户操作, 会议讨论可能在论坛有相关帖子 ] } }; 测试与验证1. 单元测试编写为每个工具编写全面的单元测试// tests/getMyCustomTool.test.js import { describe, it, expect, beforeEach, afterEach, vi } from vitest; import { getMyCustomTool } from ../src/tools/getMyCustomTool.js; describe(getMyCustomTool工具测试, () { beforeEach(() { vi.useFakeTimers(); // 模拟fetch global.fetch vi.fn(); }); afterEach(() { vi.useRealTimers(); vi.restoreAllMocks(); }); describe(列表查询, () { it(应该成功返回数据列表, async () { const mockData [{ name: 测试数据, status: active }]; global.fetch.mockResolvedValue({ ok: true, json: () Promise.resolve(mockData) }); const result await getMyCustomTool({ query_type: list }); expect(result).toContain(自定义数据列表); expect(result).toContain(测试数据); }); it(应该处理空数据情况, async () { global.fetch.mockResolvedValue({ ok: true, json: () Promise.resolve([]) }); const result await getMyCustomTool({ query_type: list }); expect(result).toContain(未找到相关数据); }); }); describe(错误处理, () { it(应该处理网络错误, async () { global.fetch.mockRejectedValue(new Error(网络错误)); const result await getMyCustomTool({ query_type: list }); expect(result).toContain(查询失败); }); it(应该处理API错误响应, async () { global.fetch.mockResolvedValue({ ok: false, status: 500 }); const result await getMyCustomTool({ query_type: list }); expect(result).toContain(API请求失败); }); }); });2. 集成测试验证工具在MCP环境下的集成# 启动开发服务器 npm start # 测试工具调用 curl -X POST http://localhost:3000/message \ -H Content-Type: application/json \ -d { tool: get_my_custom_tool, params: { query_type: list } } 性能优化建议1. 缓存优化策略// 根据数据特性选择缓存时间 const CACHE_CONFIG { STATIC_DATA: 60 * 60 * 1000, // 1小时静态数据 DYNAMIC_DATA: 15 * 60 * 1000, // 15分钟动态数据 USER_DATA: 24 * 60 * 60 * 1000, // 24小时用户数据 }; // 实现智能缓存失效 class SmartCache { constructor(duration) { this.cache new Map(); this.duration duration; } get(key) { const item this.cache.get(key); if (!item) return null; if (Date.now() item.expiry) { this.cache.delete(key); return null; } return item.data; } set(key, data) { this.cache.set(key, { data, expiry: Date.now() this.duration }); } // 批量清理过期缓存 cleanup() { const now Date.now(); for (const [key, item] of this.cache.entries()) { if (now item.expiry) { this.cache.delete(key); } } } }2. 请求优化// 使用AbortSignal控制超时 const controller new AbortController(); const timeoutId setTimeout(() controller.abort(), 15000); try { const response await fetch(url, { signal: controller.signal, headers: { User-Agent: openEuler-portal-mcp/1.0, Accept: application/json } }); clearTimeout(timeoutId); // 处理响应 } catch (error) { clearTimeout(timeoutId); if (error.name AbortError) { throw new Error(请求超时); } throw error; } 工具间的依赖关系了解现有工具的依赖关系有助于设计新工具工具名称依赖的服务依赖的工具输出格式get_docs_infodocsVersionServiceget_docs_search_contentMarkdown文档get_docs_search_contentdocsVersionServiceget_docs_info结构化搜索结果execute_user_operation无get_meeting_info操作结果推荐get_development_info无get_issue_info,get_pull_request_info开发活动报告 实际扩展案例案例1添加新的API查询工具假设需要添加一个查询openEuler社区活动的新工具// src/tools/getCommunityEvents.js export async function getCommunityEvents(params) { const { event_type, date_range } params; // 实现逻辑... } export const toolDefinition { name: get_community_events, description: 查询openEuler社区活动信息, inputSchema: { type: object, properties: { event_type: { type: string, description: 活动类型meetup、webinar、hackathon, enum: [meetup, webinar, hackathon] }, date_range: { type: string, description: 时间范围upcoming、past、all } } } };案例2扩展现有工具功能在现有工具基础上添加新功能// 在getSigInfo.js中添加新功能 async function getSigStatistics(sigName, period) { // 添加SIG统计信息查询 const stats await fetchSigStats(sigName, period); let output ## ${sigName} SIG 统计信息${period}\n\n; output addField(活跃成员数, stats.active_members); output addField(PR数量, stats.pr_count); output addField(Issue数量, stats.issue_count); output addField(代码提交数, stats.commits); return output; } 学习资源与参考1. 核心参考文件项目架构文档docs/ARCHITECTURE.md - 详细的项目架构说明工具选择机制docs/TOOL_SELECTION.md - AI如何选择工具的机制主入口文件src/index.js - 工具注册和MCP服务器配置2. 最佳实践示例复杂工具示例src/tools/getSigInfo.js - 包含缓存、模糊匹配的完整实现API集成示例src/tools/getCveInfo.js - 外部API调用和错误处理用户操作示例src/tools/executeUserOperation.js - Token验证和用户操作3. 实用工具模块格式化工具src/utils/formatHelpers.js - 统一的输出格式化函数推荐系统src/utils/toolRecommendations.js - 智能推荐配置 常见问题与解决方案问题1工具无法被AI识别解决方案确保在src/index.js中正确导入和注册工具检查toolDefinition的name属性是否符合MCP命名规范验证inputSchema定义是否正确问题2API调用失败解决方案添加适当的超时控制15秒实现重试机制提供友好的错误信息问题3缓存不生效解决方案检查缓存时间戳逻辑确保缓存键的唯一性验证缓存清理机制问题4推荐系统不工作解决方案在toolRecommendations.js中正确配置推荐关系确保在工具函数中调用appendRecommendation检查推荐场景描述是否清晰 未来扩展方向1. 数据源扩展集成更多openEuler官方API支持第三方数据源添加离线数据支持2. 功能增强实现更复杂的查询条件添加数据可视化输出支持批量操作3. 性能优化实现增量更新添加压缩传输优化内存使用4. 用户体验改进添加更多上下文感知实现个性化推荐支持多语言输出 总结扩展openEuler-portal-mcp的自定义查询工具是一个系统化的过程需要遵循项目的架构规范和最佳实践。通过本文的指南您可以快速创建新工具按照标准模板创建工具文件正确集成到系统在主入口注册并配置推荐确保代码质量编写全面的单元测试优化性能合理使用缓存和错误处理提供良好用户体验集成智能推荐系统记住每个新工具都应该遵循单一职责原则实现适当的错误处理集成缓存机制提供清晰的文档包含完整的测试用例通过遵循这些指导原则您可以为openEuler-portal-mcp项目贡献高质量的工具扩展帮助更多开发者通过AI工具更好地访问openEuler社区资源。【免费下载链接】openEuler-portal-mcpThe repository of openEuler portal MCP Server项目地址: https://gitcode.com/openeuler/openEuler-portal-mcp创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考