Skip to main content
Glama
Huangwh826

Halo MCP Server

by Huangwh826
server.py33 kB
"""MCP 服务器实现。""" from typing import Any, Dict, Optional from loguru import logger from mcp.server import Server from mcp.server.stdio import stdio_server from mcp.types import Prompt, Tool from halo_mcp_server.client import HaloClient from halo_mcp_server.config import settings from halo_mcp_server.prompts import BLOG_PROMPTS from halo_mcp_server.tools.attachment_tools import ATTACHMENT_TOOLS from halo_mcp_server.tools.category_tools import CATEGORY_TOOLS from halo_mcp_server.tools.post_tools import ( create_post_tool, delete_post_tool, get_post_draft_tool, get_post_tool, list_my_posts_tool, publish_post_tool, unpublish_post_tool, update_post_draft_tool, update_post_tool, POST_TOOLS, ) from halo_mcp_server.tools.tag_tools import TAG_TOOLS from halo_mcp_server.tools.site_tools import SITE_TOOLS from halo_mcp_server.models.common import ToolResult # Create MCP server instance app = Server(settings.mcp_server_name) # Global Halo client instance halo_client: Optional[HaloClient] = None async def get_halo_client() -> HaloClient: """获取或创建 Halo 客户端实例。""" global halo_client if halo_client is None: halo_client = HaloClient() await halo_client.connect() await halo_client.authenticate() logger.info("Halo 客户端已初始化") return halo_client @app.list_prompts() async def list_prompts() -> list[Prompt]: """列出可用的 MCP 提示。""" logger.debug("正在列出提示...") logger.info(f"已注册 {len(BLOG_PROMPTS)} 个提示") return BLOG_PROMPTS @app.get_prompt() async def get_prompt(name: str, arguments: Dict[str, str]) -> str: """根据参数获取提示内容。""" logger.info(f"获取提示:{name}") logger.debug(f"参数:{arguments}") # Find the prompt by name prompt = None for p in BLOG_PROMPTS: if p.name == name: prompt = p break if not prompt: raise ValueError(f"未找到提示:'{name}'") # Generate prompt content based on the prompt type if name == "halo_blog_writing_assistant": return _generate_writing_assistant_prompt(arguments) elif name == "halo_content_optimizer": return _generate_content_optimizer_prompt(arguments) elif name == "halo_seo_optimizer": return _generate_seo_optimizer_prompt(arguments) elif name == "halo_title_generator": return _generate_title_generator_prompt(arguments) elif name == "halo_excerpt_generator": return _generate_excerpt_generator_prompt(arguments) elif name == "halo_tag_suggester": return _generate_tag_suggester_prompt(arguments) elif name == "halo_category_suggester": return _generate_category_suggester_prompt(arguments) elif name == "halo_content_translator": return _generate_content_translator_prompt(arguments) elif name == "halo_content_proofreader": return _generate_content_proofreader_prompt(arguments) elif name == "halo_series_planner": return _generate_series_planner_prompt(arguments) else: raise ValueError(f"未知的提示:{name}") def _generate_writing_assistant_prompt(args: Dict[str, str]) -> str: """生成写作助手提示。""" topic = args.get("topic", "") target_audience = args.get("target_audience", "一般读者") article_type = args.get("article_type", "技术文章") word_count = args.get("word_count", "1500") tone = args.get("tone", "专业且易懂") return f"""你是一位专业的博客写作助手。请根据以下要求创建一篇高质量的博客文章: **文章主题:** {topic} **目标读者:** {target_audience} **文章类型:** {article_type} **期望字数:** {word_count} 字 **写作风格:** {tone} 请按照以下结构创建文章: 1. **引人入胜的开头** - 用一个有趣的事实、问题或故事开始 - 明确说明读者将从这篇文章中获得什么 2. **清晰的主体内容** - 使用小标题组织内容 - 提供具体的例子和实用建议 - 确保逻辑清晰,层次分明 3. **有力的结尾** - 总结关键要点 - 提供行动建议或下一步指导 **写作要求:** - 使用 HTML 富文本(兼容 ProseMirror + CodeMirror) - 语言{tone},适合{target_audience} - 包含适当的代码示例(如果适用) - 添加相关的图片描述或图表说明 - 确保内容原创且有价值 请开始创作这篇关于"{topic}"的文章。""" def _generate_content_optimizer_prompt(args: Dict[str, str]) -> str: """生成内容优化提示。""" content = args.get("content", "") optimization_focus = args.get("optimization_focus", "整体优化") target_length = args.get("target_length", "保持不变") return f"""你是一位专业的内容优化专家。请优化以下文章内容: **优化重点:** {optimization_focus} **长度要求:** {target_length} **原始内容:** {content} **优化指导原则:** 1. **可读性优化** - 简化复杂句子 - 使用更清晰的表达 - 改善段落结构 2. **结构优化** - 添加或改进小标题 - 重新组织内容逻辑 - 确保信息流畅 3. **吸引力提升** - 使用更生动的语言 - 添加具体例子 - 增强读者参与感 4. **专业性保持** - 确保技术准确性 - 保持专业术语的正确使用 - 维护内容的权威性 请提供优化后的内容,并简要说明主要改进点。""" def _generate_seo_optimizer_prompt(args: Dict[str, str]) -> str: """生成 SEO 优化提示。""" title = args.get("title", "") content = args.get("content", "") target_keywords = args.get("target_keywords", "") meta_description_length = args.get("meta_description_length", "160") return f"""你是一位 SEO 优化专家。请为以下文章提供 SEO 优化建议: **原始标题:** {title} **目标关键词:** {target_keywords} **元描述长度限制:** {meta_description_length} 字符 **文章内容:** {content} **请提供以下 SEO 优化建议:** 1. **标题优化** - 提供 3-5 个 SEO 友好的标题选项 - 确保包含主要关键词 - 保持标题吸引力和可读性 2. **元描述** - 创建简洁有力的元描述 - 包含关键词但避免堆砌 - 在 {meta_description_length} 字符内 3. **内容优化建议** - 关键词密度和分布建议 - 内部链接机会 - 图片 alt 文本建议 4. **结构化数据建议** - 适合的 Schema 标记 - 标题层级优化 5. **URL 建议** - SEO 友好的 URL slug 请确保所有建议都符合搜索引擎最佳实践。""" def _generate_title_generator_prompt(args: Dict[str, str]) -> str: """生成标题生成器提示。""" content_summary = args.get("content_summary", "") title_style = args.get("title_style", "多样化") title_count = args.get("title_count", "5") return f"""你是一位专业的标题创作专家。请为以下内容生成吸引人的标题: **内容摘要:** {content_summary} **标题风格:** {title_style} **生成数量:** {title_count} 个 **标题创作要求:** 1. **吸引力** - 激发读者好奇 - 突出文章价值 - 使用有力的词汇 2. **清晰性** - 准确反映内容 - 避免误导性表述 - 易于理解 3. **SEO 友好** - 包含相关关键词 - 长度适中(50-60 字符) - 避免特殊符号 **不同风格的标题类型:** - **问题式:** 以疑问句形式吸引读者 - **数字式:** 使用具体数字增加可信度 - **对比式:** 突出前后对比或优劣比较 - **悬念式:** 制造悬念激发好奇 - **实用式:** 强调实用价值和解决方案 请生成 {title_count} 个不同风格的标题选项,并简要说明每个标题的特点。""" def _generate_excerpt_generator_prompt(args: Dict[str, str]) -> str: """生成摘要生成器提示。""" content = args.get("content", "") excerpt_length = args.get("excerpt_length", "中等长度") excerpt_style = args.get("excerpt_style", "概述式") return f"""你是一位专业的内容摘要专家。请为以下文章创建精彩的摘要: **摘要长度:** {excerpt_length} **摘要风格:** {excerpt_style} **文章内容:** {content} **摘要创作要求:** 1. **准确性** - 准确反映文章核心内容 - 不包含文章中没有的信息 - 保持与原文一致的观点 2. **吸引力** - 激发读者阅读兴趣 - 突出文章的独特价值 - 使用引人入胜的语言 3. **完整性** - 涵盖文章主要观点 - 体现文章结构逻辑 - 给读者完整的预期 **不同风格的摘要:** - **概述式:** 简洁概括文章主要内容 - **亮点式:** 突出文章最有价值的部分 - **问题式:** 以问题引导读者思考 **长度指导:** - 短:80-120 字 - 中:120-200 字 - 长:200-300 字 请创建一个{excerpt_length}的{excerpt_style}摘要。""" def _generate_tag_suggester_prompt(args: Dict[str, str]) -> str: """生成标签建议提示。""" title = args.get("title", "") content = args.get("content", "") existing_tags = args.get("existing_tags", "") tag_count = args.get("tag_count", "5-8") return f"""你是一位专业的内容标签专家。请为以下文章推荐合适的标签: **文章标题:** {title} **现有标签:** {existing_tags} **建议数量:** {tag_count} 个 **文章内容:** {content} **标签推荐原则:** 1. **相关性** - 直接相关的技术、概念或主题 - 反映文章核心内容 - 帮助读者快速理解文章主题 2. **搜索价值** - 读者可能搜索的关键词 - 有助于内容发现 - 平衡热门和长尾标签 3. **分类层次** - 技术类标签(编程语言、框架、工具等) - 概念类标签(设计模式、最佳实践等) - 应用类标签(Web开发、移动开发等) 4. **标签质量** - 避免过于宽泛的标签 - 避免重复或相似的标签 - 使用标准化的术语 **请提供:** 1. 推荐的 {tag_count} 个标签 2. 每个标签的选择理由 3. 标签的优先级排序 注意:如果提供了现有标签列表,请优先从中选择合适的标签,必要时可以建议新标签。""" def _generate_category_suggester_prompt(args: Dict[str, str]) -> str: """生成分类建议提示。""" title = args.get("title", "") content = args.get("content", "") existing_categories = args.get("existing_categories", "") return f"""你是一位专业的内容分类专家。请为以下文章推荐最合适的分类: **文章标题:** {title} **现有分类:** {existing_categories} **文章内容:** {content} **分类推荐原则:** 1. **主题匹配** - 分析文章的核心主题 - 确定文章的主要领域 - 考虑内容的深度和广度 2. **层级结构** - 优先选择最具体的分类 - 考虑分类的层级关系 - 避免过于宽泛的分类 3. **读者期望** - 考虑读者的浏览习惯 - 便于内容发现和导航 - 保持分类的一致性 **分析步骤:** 1. 识别文章的主要技术栈或领域 2. 确定文章的类型(教程、经验分享、工具介绍等) 3. 评估内容的技术深度 4. 考虑目标读者群体 **请提供:** 1. 最推荐的主分类(1个) 2. 可选的次要分类(如果适用) 3. 分类选择的详细理由 4. 如果现有分类不合适,建议新的分类名称 注意:如果提供了现有分类列表,请优先从中选择最合适的分类。""" def _generate_content_translator_prompt(args: Dict[str, str]) -> str: """生成内容翻译提示。""" content = args.get("content", "") target_language = args.get("target_language", "") preserve_formatting = args.get("preserve_formatting", "是") translation_style = args.get("translation_style", "意译") return f"""你是一位专业的技术内容翻译专家。请将以下内容翻译成{target_language}: **翻译风格:** {translation_style} **保持格式:** {preserve_formatting} **原始内容:** {content} **翻译要求:** 1. **准确性** - 准确传达原文含义 - 保持技术术语的准确性 - 不遗漏重要信息 2. **流畅性** - 符合目标语言的表达习惯 - 语句通顺自然 - 避免直译造成的生硬表达 3. **专业性** - 使用标准的技术术语 - 保持专业文档的风格 - 确保术语一致性 4. **格式保持** {"- 保持 HTML 结构不变(兼容 ProseMirror + CodeMirror)" if preserve_formatting == "是" else "- 可以调整格式以适应目标语言"} - 保持代码块和链接 - 维护列表和表格结构 **翻译风格说明:** - **直译:** 尽可能保持原文结构 - **意译:** 优先考虑目标语言的表达习惯 - **本地化:** 适应目标文化和使用习惯 请提供高质量的{target_language}翻译。""" def _generate_content_proofreader_prompt(args: Dict[str, str]) -> str: """生成内容校对提示。""" content = args.get("content", "") language = args.get("language", "中文") check_focus = args.get("check_focus", "全面检查") return f"""你是一位专业的{language}内容校对专家。请仔细校对以下内容: **校对重点:** {check_focus} **内容语言:** {language} **待校对内容:** {content} **校对检查项目:** 1. **语法检查** - 句法结构是否正确 - 时态和语态使用 - 标点符号使用 2. **拼写检查** - 错别字识别 - 专业术语拼写 - 英文单词拼写 3. **表达优化** - 语句是否通顺 - 表达是否清晰 - 用词是否准确 4. **逻辑检查** - 内容逻辑是否清晰 - 前后是否一致 - 论述是否完整 5. **格式检查** - HTML 结构是否正确(兼容 ProseMirror + CodeMirror) - 标题层级是否合理 - 列表格式是否统一 **请提供:** 1. 校对后的完整内容 2. 主要修改说明 3. 改进建议 注意:保持原文的风格和观点,只进行必要的语言和格式修正。""" def _generate_series_planner_prompt(args: Dict[str, str]) -> str: """生成系列规划提示。""" series_topic = args.get("series_topic", "") target_audience = args.get("target_audience", "技术开发者") article_count = args.get("article_count", "5-8") difficulty_progression = args.get("difficulty_progression", "由浅入深") return f"""你是一位专业的技术内容规划专家。请为以下主题规划一个系列文章: **系列主题:** {series_topic} **目标读者:** {target_audience} **文章数量:** {article_count} 篇 **难度递进:** {difficulty_progression} **系列规划要求:** 1. **整体结构** - 设计清晰的学习路径 - 确保内容的连贯性 - 平衡理论和实践 2. **难度设计** - 根据"{difficulty_progression}"安排内容 - 确保每篇文章都有适当的挑战性 - 为不同水平的读者提供价值 3. **内容覆盖** - 涵盖主题的核心概念 - 包含实际应用场景 - 提供最佳实践指导 **请提供详细的系列规划:** 1. **系列概述** - 系列的总体目标 - 读者将获得的技能或知识 - 预计的学习时间 2. **文章列表** - 每篇文章的标题 - 主要内容概述(100-200字) - 难度级别(初级/中级/高级) - 预计字数 3. **学习路径** - 文章之间的关联关系 - 前置知识要求 - 实践项目建议 4. **补充资源** - 推荐的参考资料 - 相关工具和库 - 社区资源 请确保整个系列对{target_audience}具有实际价值和可操作性。""" @app.list_tools() async def list_tools() -> list[Tool]: """列出可用的 MCP 工具。""" logger.debug("正在列出工具...") # Post management tools post_tools = [ Tool( name="list_my_posts", description="列出当前用户的所有文章,支持按发布状态、关键词和分类筛选", inputSchema={ "type": "object", "properties": { "page": { "type": "number", "description": "页码(默认:0)", "default": 0, }, "size": { "type": "number", "description": "每页数量(默认:20)", "default": 20, }, "publish_phase": { "type": "string", "description": "按发布状态筛选:DRAFT(草稿)、PENDING_APPROVAL(待审核)、PUBLISHED(已发布)、FAILED(失败)", "enum": ["DRAFT", "PENDING_APPROVAL", "PUBLISHED", "FAILED"], }, "keyword": { "type": "string", "description": "搜索关键词", }, "category": { "type": "string", "description": "按分类名称筛选(包含子分类)", }, }, }, ), Tool( name="get_post", description="获取指定文章的详细信息,包括内容、元数据和设置", inputSchema={ "type": "object", "properties": { "name": { "type": "string", "description": "文章名称/标识符(必填)", }, }, "required": ["name"], }, ), Tool( name="create_post", description="创建一篇新的博客文章(内容推荐使用 HTML 富文本,兼容 ProseMirror + CodeMirror,也支持 Markdown),包括标题、内容、分类、标签等设置", inputSchema={ "type": "object", "properties": { "title": { "type": "string", "description": "文章标题(必填)", }, "content": { "type": "string", "description": "文章内容,推荐使用 HTML 富文本(兼容 ProseMirror + CodeMirror),也支持 Markdown(必填)", }, "excerpt": { "type": "string", "description": "文章摘要/简介", }, "slug": { "type": "string", "description": "URL 别名(不提供则自动生成)", }, "categories": { "type": "array", "items": {"type": "string"}, "description": "分类名称列表", }, "tags": { "type": "array", "items": {"type": "string"}, "description": "标签名称列表", }, "content_format": { "type": "string", "description": "内容格式:MARKDOWN、HTML 或 AUTO(默认:HTML)", "enum": ["MARKDOWN", "HTML", "AUTO"], "default": "HTML", }, "cover": { "type": "string", "description": "封面图片 URL", }, "allow_comment": { "type": "boolean", "description": "是否允许评论(默认:true)", "default": True, }, "pinned": { "type": "boolean", "description": "是否置顶(默认:false)", "default": False, }, "visible": { "type": "string", "description": "可见性:PUBLIC(公开)或 PRIVATE(私密),默认:PUBLIC", "enum": ["PUBLIC", "PRIVATE"], "default": "PUBLIC", }, "publish_immediately": { "type": "boolean", "description": "创建后立即发布(默认:false)", "default": False, }, }, "required": ["title", "content"], }, ), Tool( name="update_post", description="更新现有文章(内容推荐使用 HTML 富文本,兼容 ProseMirror + CodeMirror,也支持 Markdown),包括标题、内容、分类、标签或其他设置", inputSchema={ "type": "object", "properties": { "name": { "type": "string", "description": "文章名称/标识符(必填)", }, "title": { "type": "string", "description": "新标题", }, "content": { "type": "string", "description": "新内容,推荐使用 HTML 富文本(兼容 ProseMirror + CodeMirror),也支持 Markdown", }, "excerpt": { "type": "string", "description": "新摘要", }, "categories": { "type": "array", "items": {"type": "string"}, "description": "新分类名称列表", }, "tags": { "type": "array", "items": {"type": "string"}, "description": "新标签列表(使用标签的内部标识符 metadata.name)", }, "content_format": { "type": "string", "description": "内容格式:MARKDOWN、HTML 或 AUTO(默认:HTML)", "enum": ["MARKDOWN", "HTML", "AUTO"], "default": "HTML", }, "cover": { "type": "string", "description": "新封面图片 URL", }, "allow_comment": { "type": "boolean", "description": "是否允许评论", }, "pinned": { "type": "boolean", "description": "是否置顶", }, "visible": { "type": "string", "description": "可见性:PUBLIC(公开)或 PRIVATE(私密)", "enum": ["PUBLIC", "PRIVATE"], }, }, "required": ["name"], }, ), Tool( name="publish_post", description="发布草稿文章,使其公开可见", inputSchema={ "type": "object", "properties": { "name": { "type": "string", "description": "文章名称/标识符(必填)", }, }, "required": ["name"], }, ), Tool( name="unpublish_post", description="取消发布文章,使其转换回草稿状态", inputSchema={ "type": "object", "properties": { "name": { "type": "string", "description": "文章名称/标识符(必填)", }, }, "required": ["name"], }, ), Tool( name="delete_post", description="删除文章(移至回收站)", inputSchema={ "type": "object", "properties": { "name": { "type": "string", "description": "文章名称/标识符(必填)", }, }, "required": ["name"], }, ), Tool( name="get_post_draft", description="获取文章的草稿版本及可编辑内容", inputSchema={ "type": "object", "properties": { "name": { "type": "string", "description": "文章名称/标识符(必填)", }, "patched": { "type": "boolean", "description": "是否同时返回补丁(修订/差异)后的内容与原始内容(默认:false)", "default": False, }, }, "required": ["name"], }, ), Tool( name="update_post_draft", description="更新文章草稿内容(推荐使用 HTML 富文本,兼容 ProseMirror + CodeMirror,也支持 Markdown),不会发布", inputSchema={ "type": "object", "properties": { "name": { "type": "string", "description": "文章名称/标识符(必填)", }, "content": { "type": "string", "description": "草稿内容,推荐使用 HTML 富文本(兼容 ProseMirror + CodeMirror),也支持 Markdown(必填)", }, }, "required": ["name", "content"], }, ), ] # Combine all tools all_tools = POST_TOOLS + CATEGORY_TOOLS + TAG_TOOLS + ATTACHMENT_TOOLS + SITE_TOOLS logger.info(f"Registered {len(all_tools)} tools") return all_tools @app.call_tool() async def call_tool(name: str, arguments: Dict[str, Any]) -> list[Any]: """处理工具执行。""" logger.info(f"执行工具:{name}") logger.debug(f"参数:{arguments}") try: client = await get_halo_client() # Route to appropriate tool handler if name == "list_my_posts": result = await list_my_posts_tool(client, arguments) elif name == "get_post": result = await get_post_tool(client, arguments) elif name == "create_post": result = await create_post_tool(client, arguments) elif name == "update_post": result = await update_post_tool(client, arguments) elif name == "publish_post": result = await publish_post_tool(client, arguments) elif name == "unpublish_post": result = await unpublish_post_tool(client, arguments) elif name == "delete_post": result = await delete_post_tool(client, arguments) elif name == "get_post_draft": result = await get_post_draft_tool(client, arguments) elif name == "update_post_draft": result = await update_post_draft_tool(client, arguments) # Category tools elif name == "list_categories": from .tools.category_tools import list_categories_tool result = await list_categories_tool(client, arguments) elif name == "get_category": from .tools.category_tools import get_category_tool result = await get_category_tool(client, arguments) elif name == "create_category": from .tools.category_tools import create_category_tool result = await create_category_tool(client, arguments) elif name == "update_category": from .tools.category_tools import update_category_tool result = await update_category_tool(client, arguments) elif name == "delete_category": from .tools.category_tools import delete_category_tool result = await delete_category_tool(client, arguments) elif name == "get_category_posts": from .tools.category_tools import get_posts_under_category_tool result = await get_posts_under_category_tool(client, arguments) # Tag tools elif name == "list_tags": from .tools.tag_tools import list_tags_tool result = await list_tags_tool(client, arguments) elif name == "get_tag": from .tools.tag_tools import get_tag_tool result = await get_tag_tool(client, arguments) elif name == "create_tag": from .tools.tag_tools import create_tag_tool result = await create_tag_tool(client, arguments) elif name == "update_tag": from .tools.tag_tools import update_tag_tool result = await update_tag_tool(client, arguments) elif name == "delete_tag": from .tools.tag_tools import delete_tag_tool result = await delete_tag_tool(client, arguments) elif name == "get_tag_posts": from .tools.tag_tools import get_posts_under_tag_tool result = await get_posts_under_tag_tool(client, arguments) elif name == "list_console_tags": from .tools.tag_tools import list_console_tags_tool result = await list_console_tags_tool(client, arguments) # Attachment tools elif name == "list_attachments": from .tools.attachment_tools import list_attachments_tool result = await list_attachments_tool(client, arguments) elif name == "get_attachment": from .tools.attachment_tools import get_attachment_tool result = await get_attachment_tool(client, arguments) elif name == "upload_attachment": from .tools.attachment_tools import upload_attachment_tool result = await upload_attachment_tool(client, arguments) elif name == "upload_attachment_from_url": from .tools.attachment_tools import upload_attachment_from_url_tool result = await upload_attachment_from_url_tool(client, arguments) elif name == "delete_attachment": from .tools.attachment_tools import delete_attachment_tool result = await delete_attachment_tool(client, arguments) elif name == "list_attachment_groups": from .tools.attachment_tools import list_attachment_groups_tool result = await list_attachment_groups_tool(client, arguments) elif name == "create_attachment_group": from .tools.attachment_tools import create_attachment_group_tool result = await create_attachment_group_tool(client, arguments) elif name == "get_attachment_policies": from .tools.attachment_tools import list_storage_policies_tool result = await list_storage_policies_tool(client, arguments) # Site tools elif name == "get_halo_base_url": from .tools.site_tools import get_halo_base_url_tool result = await get_halo_base_url_tool(client, arguments) else: return [{"type": "text", "text": f"Unknown tool: {name}"}] logger.info(f"Tool {name} executed successfully") return [{"type": "text", "text": result}] except Exception as e: logger.error(f"Error executing {name}: {e}", exc_info=True) error_result = ToolResult.error_result(f"Error executing {name}: {str(e)}") return [{"type": "text", "text": error_result.model_dump_json()}] async def run_server() -> None: """使用 stdio 传输运行 MCP 服务器。""" logger.info("使用 stdio 传输启动 MCP 服务器...") async with stdio_server() as (read_stream, write_stream): logger.info("服务器已就绪,正在等待请求...") await app.run(read_stream, write_stream, app.create_initialization_options()) # 导出 server 实例供测试使用 server = app if __name__ == "__main__": import asyncio asyncio.run(run_server())

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Huangwh826/halo-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server