ask-experts-mcp
v0.2.0
Published
MCP server that routes domain-specific questions to expert LLMs (Hunyuan, Qwen, etc.) via OpenAI-compatible APIs
Maintainers
Readme
ask-experts-mcp
让任意支持 MCP 的客户端(Claude Code / Cursor / Cline / Continue / Zed 等)在遇到自己不擅长的领域知识时,主动咨询训练数据更对口的国产专家模型。
写微信小程序问 wx.login、写阿里云 OSS 直传、写飞书机器人——这些中文互联网生态特有的领域,海外训练为主的模型(Claude / GPT / Gemini)覆盖比国产模型薄。ask-experts-mcp 给调用方模型暴露一组按"擅长领域"命名的工具(ask_hunyuan、ask_qwen、ask_doubao),让它自己读工具描述、自己挑该问谁——不需要切 chat 窗口、复制粘贴。
| 领域 | 谁更熟 | |---|---| | 微信生态(小程序、公众号、企业微信、视频号、QQ、腾讯云) | 混元 | | 阿里云 / 钉钉 / 通义系产品 / 1688 / 淘宝 | Qwen | | 抖音 / 飞书 / 火山引擎 / 字节系开放平台 | 豆包 |
本工具只做两件事:路由(通过工具描述告诉调用方"这种问题该找谁")+ 接入(任意 OpenAI 兼容端点,baseURL + model 配置化)。
不做的事:不做内置 LLM 自动选 expert(那会增加一次模型调用、和调用方判断打架);不做多模型对比 / 投票;不做对话历史(每次 ask_* 单轮无状态);不做流式;不持有用户的 API Key,不做 SaaS 托管。
快速开始
要求 Node.js ≥ 18。
路径 A:零配置(推荐)
只要在 MCP 客户端的 env 里塞 API Key,不需要写任何 JSON 配置文件。ask-experts-mcp 会扫描以下环境变量,匹配到哪家就启用哪家(用 preset 默认 model 和领域描述):
| env 变量 | 启用的 expert | 工具名 |
|---|---|---|
| HUNYUAN_API_KEY | 腾讯混元 | ask_hunyuan |
| DASHSCOPE_API_KEY(或 QWEN_API_KEY) | 阿里通义千问 | ask_qwen |
| DOUBAO_API_KEY(或 ARK_API_KEY) | 字节豆包 | ask_doubao |
Claude Code 一行命令搞定:
claude mcp add ask-experts \
-e HUNYUAN_API_KEY=$HUNYUAN_API_KEY \
-e DASHSCOPE_API_KEY=$DASHSCOPE_API_KEY \
-e DOUBAO_API_KEY=$DOUBAO_API_KEY \
-- npx -y ask-experts-mcp或者写到 mcpServers 配置里(各客户端通用格式):
{
"mcpServers": {
"ask-experts": {
"command": "npx",
"args": ["-y", "ask-experts-mcp"],
"env": {
"HUNYUAN_API_KEY": "sk-xxx",
"DASHSCOPE_API_KEY": "sk-yyy",
"DOUBAO_API_KEY": "sk-zzz"
}
}
}
}| 客户端 | 配置文件位置 |
|---|---|
| Claude Code | ./.mcp.json(项目级)或 ~/.claude.json 内 mcpServers 字段 |
| Cursor | ~/.cursor/mcp.json 或 ./.cursor/mcp.json(项目级) |
| Cline / Continue | 各自插件的 MCP settings 面板,字段一致 |
| Zed | ~/.config/zed/settings.json 内 context_servers 字段 |
| Windsurf | ~/.codeium/windsurf/mcp_config.json |
验证:
HUNYUAN_API_KEY=sk-xxx npx -y ask-experts-mcp check # 给每个 expert 发 ping
HUNYUAN_API_KEY=sk-xxx npx -y ask-experts-mcp list # 打印实际生效的工具描述然后在客户端里说:"帮我实现微信小程序登录,要拿到 unionid"——调用方模型会自己挑出 ask_hunyuan 来调用。
调用方模型必须支持 tool use(function calling)。
路径 B:JSON 配置(需要自定义 model / 领域描述 / 自有 endpoint 时)
把下面文件写到 ~/.config/ask-experts/config.json(全局)或项目目录下的 .ask-experts.json(项目级):
{
"experts": [
{
"name": "hunyuan",
"provider": "hunyuan",
"apiKey": "${HUNYUAN_API_KEY}",
"model": "hunyuan-turbos-latest"
}
]
}零配置和 JSON 配置可以混用——找到任意 JSON 文件后就走 JSON 路径,env-only 仅在两个文件都不存在时兜底。
预设 provider
只内置三家有独立中文互联网生态的 provider,加上一个 custom 兜底:
| provider | baseURL | 推荐 model | 默认领域 |
|---|---|---|---|
| hunyuan | https://api.hunyuan.cloud.tencent.com/v1 | hunyuan-turbos-latest | 微信生态(小程序、公众号、企业微信、视频号)、腾讯云、QQ |
| dashscope | https://dashscope.aliyuncs.com/compatible-mode/v1 | qwen-plus-latest | 阿里云、钉钉、淘宝/天猫/1688 |
| doubao | https://ark.cn-beijing.volces.com/api/v3 | doubao-seed-1-6-250615 | 字节系、抖音、飞书、火山引擎 |
| custom | (自填) | (自填) | 任意 OpenAI 兼容端点,自填 expertise.override: true 完整定义领域 |
故意不内置 deepseek / zhipu / moonshot / openai——这些是通用模型能力的另一选择而非独立的中文生态,调用方模型本身就够用,走 ask-experts 路由价值有限。需要的话用 custom 自定义即可。
具体 model 名常调整,配置时核实。三家预设的 OpenAI 兼容性踩坑清单见 docs/openai-compat.md(主流程不踩,接 custom 用其他字段时值得先看)。
配置
文件查找与合并
按下面的顺序查找,全部存在时按规则合并:
~/.config/ask-experts/config.json(全局)${PROJECT}/.ask-experts.json(项目级)--config <path>启动参数(指定时完全替换前两层,适合 CI / 临时测试)
合并规则:
defaults:深合并,项目级字段覆盖全局同名字段experts:按namelist-merge——同名的项目级 expert 字段级覆盖全局,新名字追加;在项目级显式{ "name": "x", "enabled": false }可屏蔽全局某个 expert
随时用 ask-experts-mcp list 看合并后的最终结果。
完整示例
{
"experts": [
{
"name": "hunyuan",
"provider": "hunyuan",
"apiKey": "${HUNYUAN_API_KEY}",
"model": "hunyuan-turbos-latest"
},
{
"name": "qwen",
"provider": "dashscope",
"apiKey": "${DASHSCOPE_API_KEY}",
"model": "qwen-plus-latest",
"max_tokens": 4096,
"expertise": {
"trigger_keywords_append": ["阿里云函数计算 FC 3.0 触发器配置"]
}
},
{
"name": "internal",
"provider": "custom",
"baseURL": "https://internal-llm.company.com/v1",
"apiKey": "${INTERNAL_LLM_KEY}",
"model": "company-rag-v2",
"expertise": {
"override": true,
"summary": "公司内部知识库 RAG,擅长内部业务流程和制度",
"trigger_keywords": ["内部 SOP", "审批流程", "公司制度"],
"avoid_keywords": [],
"system_prompt": "你是公司内部知识专家,严格基于检索到的内部文档回答。"
}
}
],
"defaults": {
"timeout_ms": 60000,
"max_tokens": 2048,
"temperature": 0.3,
"log_level": "info"
}
}字段参考
| 字段 | 必填 | 说明 |
|---|---|---|
| name | ✅ | 工具名后缀,生成的工具是 ask_{name}。要求小写、字母数字下划线;全局唯一 |
| provider | ✅ | hunyuan / dashscope / doubao / custom |
| apiKey | ✅ | API Key。强烈建议只写 ${ENV_VAR} 占位符 |
| model | ✅ | 模型名 |
| baseURL | ⚠️ | provider=custom 时必填;预设 provider 自动填充 |
| enabled | ❌ | 默认 true;false 可临时禁用 |
| expertise | ❌ | 自定义擅长领域元数据;详见 调优工具描述 |
| timeout_ms / max_tokens / temperature | ❌ | per-expert 覆盖 defaults 同名字段。reasoning 模型常需更大 max_tokens |
| thinking | ❌ | 思考类模型的 reasoning_content 处理:drop(默认)/ prepend(以 <thinking> 块附在前)/ separate |
API Key 安全
- 只用
${ENV_VAR}占位符引用 API Key,不要把明文 Key 写进配置文件——配置文件常被 git 追踪、被备份工具同步,明文 Key 是常见泄露源 - 启动时对所有明文 Key 字段会打 warning
- 环境变量未设置时,该 expert 自动 disable + warning(不阻断启动)
调优工具描述
整个产品的成败取决于一件事:调用方模型看到工具描述能不能正确判断该不该调用、该调用哪个。
每个 ask_* 工具的 description 由三部分拼成:
- WHEN(
trigger_keywords)——精确列出领域关键词 - WHEN NOT(
avoid_keywords)——防止过度调用 - HOW——固定的提问技巧提示
expertise 字段级合并语义
expertise 与预设的合并是字段级深合并,不是整体覆盖。这样想加一个关键词不需要把整段预设抄一遍。
| 用户写法 | 效果 |
|---|---|
| 不写 expertise | 完全使用预设 |
| expertise.summary: "..." | summary 替换;其他字段保留预设 |
| expertise.summary_append: "..." | summary 是"预设 + 换行 + 用户值" |
| expertise.trigger_keywords: [...] | 列表整体替换 |
| expertise.trigger_keywords_append: [...] | 追加到预设列表末尾 |
| expertise.avoid_keywords / ..._append | 同上 |
| expertise.system_prompt: "..." | 完全替换 system prompt(此时 response_language 不再注入) |
| expertise.system_prompt_append: "..." | 在默认 system prompt 末尾追加(常用于加业务约束) |
| expertise.override: true | 整体替换,未列出字段视为空。custom provider 必须这么写 |
调优反馈循环
- 改
~/.config/ask-experts/config.json的trigger_keywords - 跑
ask-experts-mcp list——立刻看到最终 description - 在 MCP 客户端里测几条该领域的问题,看调用方模型是否触发该 expert
- 不触发就加更具体的关键词;过度触发就加
avoid_keywords或缩小trigger
长度上限
每个 description 上限 1000 字符(约 250 tokens)。超长时按以下优先级保留 → 截断:
- 首行 summary —— 始终保留
✅ 何时调用—— 始终保留;实在装不下时按数量截断,末尾补(...省略 K 条)❌ 何时不调用—— 装不下时整段去掉📝 提问技巧—— 最先去掉
触发截断时 stderr 会打 warning,但不阻断启动。
CLI
ask-experts-mcp 启动 MCP stdio server(默认)
ask-experts-mcp list 打印每个工具最终生效的 description(调优用)
ask-experts-mcp check 逐个 expert 发一个最小请求,报告 OK/FAIL + 延迟
ask-experts-mcp --config <path> 用指定配置文件,完全替换默认两层
ask-experts-mcp --help 帮助
ask-experts-mcp --version 版本号list:调优 description 时的核心反馈工具——改完trigger_keywords跑一次就知道最终长什么样、有没有触发截断。check:注册前 / 出问题时排查 API Key、baseURL、模型名是否对得上。退出码 0=全通过、非 0=有 expert 失败,可在 CI 当 gate。
错误处理与日志
| 错误类型 | 行为 |
|---|---|
| 401 / 403 API Key 无效 | 不重试,返回明确错误 |
| 429 限流 / 配额耗尽 | 不重试,建议切其他 expert |
| 超时(>timeout_ms) | 不重试(避免 turn 阻塞翻倍) |
| 5xx 服务端 / 连接错误 | 重试 1 次(1s 退避) |
| 模型返回空响应 | 返回错误,不返回空字符串 |
错误以 MCP isError: true 形式返回,调用方模型自行决定是否重试或换 expert。
日志:
- 写到 stderr(MCP stdio 标准做法,大多数客户端会捕获)+
~/.cache/ask-experts/log/server.log(滚动 5MB × 3) - 启动时打印一次配置加载摘要(API Key 脱敏)
- 每次工具调用打印
[timestamp] ask_<name> latency=<ms> tokens_in=<n> tokens_out=<n> status=<ok|err:<code>> defaults.log_level: "debug"可看请求 / 响应体(API Key 脱敏)
FAQ
Q: 为什么不做内置 LLM 自动选 expert?
A: 调用方模型已经在做 reasoning,顺便选 expert 是零额外成本。内置路由会增加一次 LLM 调用(延迟+费用),且和调用方的判断打架。工具描述写好,调用方选得不会比一个小模型差。
Q: 配了 5+ 个 expert,调用方选不准怎么办?
A: 文档建议 ≤3 个 expert,且 trigger_keywords 应尽量互斥。需要更多 expert 可以分项目用 .ask-experts.json 局部启用。
Q: 支持 streaming 吗?
A: 不支持。MCP 协议返回完整结果即可,客户端主对话本身的流式由 MCP 客户端自己负责。
Q: 对调用方模型有什么要求?
A: 必须支持 tool use(function calling)。Claude 全系列、GPT-4 / 4o、Gemini 1.5+、Qwen 系列等主流模型都支持。如果调用方模型本身就是国产大模型(比如用 Qwen 调 Qwen),路由价值会显著降低——这个工具的甜点是"训练数据偏海外"的调用方 + 国产专家模型。
Q: 支持思考类模型(deepseek-reasoner / qwen-thinking / doubao-seed)吗?
A: 支持。在 expert 配置里加 "thinking": "drop"(默认丢弃)/ "prepend"(<thinking> 块附在答案前)/ "separate"(用分隔符切两段)。reasoning 模型一般要把 max_tokens 调大到 4K-8K。
Q: 我自己的内部 LLM 服务可以接吗?
A: 可以,只要兼容 OpenAI Chat Completions 非 streaming + 纯 messages 接口。配 provider: "custom" + baseURL + 完整 expertise.override: true 即可。
Q: 可以让 ask_ 工具返回英文吗?*
A: 工具调用时传 response_language: "en" 即可。该参数会注入到 system prompt;若 expert 配置了完整自定义 system_prompt,则由用户自行控制语言。
贡献
欢迎贡献新 provider 或修订现有预设的擅长领域元数据。
- 改预设:编辑
presets/<provider>.json,跑npm run build重新生成 - 加 provider:新建
presets/<name>.json+ 在src/config/schema.ts的 PROVIDERS 列表加项 - 跑测试:
npm test(单测 + nock 集成测试,无需 API Key) - 跑路由质量评估:
JUDGE=qwen npm run eval:routing(实跑 LLM,需要对应 API Key,详见tests/routing/) - 跑 OpenAI 兼容性探针:
HUNYUAN_API_KEY=... DASHSCOPE_API_KEY=... DOUBAO_API_KEY=... npx tsx scripts/probe-compat.ts
PR 请在描述里说明:为什么这条 trigger / avoid 关键词是必要的——最好附上一个真实的"调用方模型选错了"的例子,或一条 eval:routing 失败的 case。
License
MIT
