搭一个带 MCP + 子 Agent 的助手
端到端实战:把 DSH 的 MCP 集成 + 子 Agent 组合,搭一个"能查外部数据、能并行干活"的助手。机制见对应页面,这里照做。
一、目标
我们要做:
一个能"查 GitHub / 数据库"的助手(走 MCP)
+ 一个能"接收后续消息持续研究"的常驻子 Agent(走 subagent)
+ 一个把这些串起来的入口
二、接一个 MCP 服务器
见 MCP 集成。把 MCP 客户端挂进 profile:
# 在你的 profile 目录,用 patch 加一个 github MCP server
~/.dsh/profiles/web/cordis.patch.yml:
- insert:
- id: mcp-github
name: '@deepseek-ai/dsh-mcp-client'
config:
serverName: github
transport: stdio
command: npx
args: ['-y', '@modelcontextprotocol/server-github']
env:
GITHUB_TOKEN: !!js process.env.GITHUB_TOKEN
重启 dsh web。现在模型能调 mcp__github__* 工具(GitHub 数据)。
若
@deepseek-ai/dsh-mcp-client在核心里,直接挂;否则先dsh plugin --profile web add。一个实例一个 server。
HTTP 型 server 用 streamable-http 传输(远程/本地 HTTP 端点):
- insert:
- id: mcp-web
name: '@deepseek-ai/dsh-mcp-client'
config:
serverName: web
transport: streamable-http
url: http://localhost:3000/mcp
headers:
Authorization: !!js '`Bearer ${process.env.MCP_TOKEN}`'
两种传输的关键字段:
| 字段 | 传输 | 说明 |
|---|---|---|
transport | 两者 | stdio(子进程)或 streamable-http(HTTP 端点),必填 |
serverName | 两者 | 工具命名空间,[A-Za-z0-9_-]{1,32},实例间唯一 |
command / args / env / cwd | stdio | 拉起子进程的命令、参数、环境、工作目录 |
url / headers | http | server 地址与附加头(如鉴权 token) |
toolCallTimeoutMs | 两者 | 单次 callTool 超时,默认 60000 |
failOnStartupError | 两者 | 初始连接/同步失败是否让插件激活失败,默认 false |
reconnect.* | 两者 | 断线重连:指数退避(500ms 起翻倍,上限 30000ms),maxAttempts 默认 10 次后放弃 |
三、工具如何被发现与命名
启动时 mcp-client 等 listTools() 回来,把每个工具用 ctx.tools.register() 注册成 mcp__<serverName>__<rawName> 形式——Claude Code / Codex 同款命名。要点:
- 命名空间隔离:两个 server 各自发布一个
search也没冲突,分别挂在mcp__github__search、mcp__web__search下。 - 重名
serverName冲突:同一serverName出现两次,后加载的实例直接 load 失败。 - 热更新同步:server 发
notifications/tools/list_changed会自动 re-sync;断线重连成功后也重新发现,恢复的这代工具替换上一代,不重复也不泄漏。 - 名字规范化:公开名限制 64 字符、
[A-Za-z0-9_-];被替换/截断时追加 12 位十六进制 hash(serverName,rawName的确定性函数),不同工具不会塌成同名。
改完 patch 后 HMR 热切:断开 + 重连,serverName 不变则工具名不变。
四、配一个常驻子 Agent
见 子 Agent。我们建一个可续子会话,让它持续吃消息再研究:
用 subagent startContinuable 建一个常驻"研究助手",
label: research-assistant,
initialPrompt: 你是常驻研究助手,随时收我的任务,用 MCP git 工具查数据。
之后随时 followup(childId, '再查一下这个 repo 的 issue') 喂新任务。子 Agent 有自己作用域、独立 header,不污染主会话。
五、派发任务与回报
常驻子 Agent 是"先建、再喂、最后回收"三段:
- 建:
subagent startContinuable返回{ childId, messageId },prompt 已入子会话 inbox,不等它开跑。 - 喂:之后用
send_message(模型侧工具)或followup(childId, ...)(服务 API)发后续,每条消息成为子 Agent 的下一个 FIFO 回合;它还在跑时,消息排队等当前回合结束,不能改正在跑的那回合。 - 回报:子 Agent 可用子会话专属的
report工具把结论回报父 Agent(帧成Background subagent <child-id> reported:);即便它没调report,结算时父会话也会收到Background subagent <child-id> finished...通知,带停止原因和最后一段话,不会静默丢结果。 - 看/停:
list_agents列出常驻子 Agent(含running/idle/ready状态),interrupt_agent只停当前回合、不清空已排队消息、不销毁子会话。
子 Agent 是独立会话、独立作用域;spawn 子会话默认看不到父会话历史,派发时给足上下文,而不是指望它记得上文。
六、组合:一条入口
把"问一句 → 子 agent 并行查/研究 → 汇总"做成一次调用:
我用 research-assistant 去查:最近 3 个 issue 里,哪些和构建有关?
把 mcp__github__ 能查的都查一遍,给我一个带链接的清单。
主 agent 把任务交给子 Agent,子 Agent 调 MCP 工具查 GitHub、回报清单。
七、常见失败排查
| 现象 | 原因与处理 |
|---|---|
| 一个工具都没出现 | 默认 failOnStartupError: false,初始连接失败时插件激活但不带任何工具;看启动日志,必要时设 true 让失败显式化 |
| 断线后工具还在但调用一直失败 | 重连用指数退避;maxAttempts(默认 10)耗尽会注销该 server 全部工具,需 HMR 重载或重启 Host |
serverName 重复报错 | 后加载的实例 load 失败;给每个实例起唯一 serverName |
| 工具名被外部注册抢占 | 整代回滚(不会只注册一半),日志报冲突 |
send_message 发了没回音 | 它只返回"已入队"确认,不返回子 Agent 回复;详情看子 Agent 自己的 transcript,或让子 Agent 用 report 回报 |
interrupt_agent 报未授权 | 只有目标子 Agent 的祖先(含跨代)能停它;自己/兄弟/过期调用都拒绝 |
| HTTP server 连不上 | streamable-http 的失败按每请求重试,不触发 supervisor 重启;确认 URL/headers 正确 |
report 收不到 | 需要父 Agent 仍是"活的直系父会话";父已结算/卸载则回报无主 |
八、验证与权限
dsh web --dump-config | grep -E "mcp|subagent" # 看 MCP + subagent 是否装载
# 会话里看 mcp 工具调用
zstdcat ~/.dsh/sessions/*/*/session.jsonl.zstd | grep -oE 'mcp__[a-z_]+' | sort -u
子 Agent 有自己的作用域;想限制它用什么工具,在 start/startContinuable 传 toolFilter。
小结
现在你有一个工作台:外部数据(MCP)+ 并行研究(子 Agent)。再加 技能 把常用流程固化、或 工作流 自动化,就能当一个稳定助手用了。