MCP 集成
MCP(Model Context Protocol) 是 AI 应用连外部数据源与工具的开源标准。DSH 通过 @deepseek-ai/dsh-mcp-client 桥接:连到外部 MCP 服务器,把它的工具注册进 ctx.tools,模型就能像用本地工具一样调用它们,名字是 mcp__<serverName>__<rawName>。
一句话
"一个插件实例 = 一个 MCP 服务器",挂在 cordis.yml/patch 里。
配置
最常用的是 stdio:本地起一个子进程当服务器。
- 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
也可以连远程服务器(streamable-http):
- 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" |
serverName | 两者 | 是 | 工具名命名空间,[A-Za-z0-9_-]{1,32},跨存活实例唯一 |
command | stdio | 是 | 要启动的可执行文件 |
args | stdio | 否 | 传给 command 的参数 |
env | stdio | 否 | 额外环境变量,合并在已剥离的宿主环境之上 |
cwd | stdio | 否 | 子进程工作目录 |
url | http | 是 | MCP 服务器地址 |
headers | http | 否 | 额外请求头(如鉴权 token) |
toolCallTimeoutMs | 两者 | 否 | 每次 callTool 超时(默认 60000) |
failOnStartupError | 两者 | 否 | 初始连接/同步失败时拒绝激活(默认 false) |
reconnect.enabled | 两者 | 否 | 掉线后自动重连(默认 true) |
reconnect.initialDelayMs | 两者 | 否 | 首次重连延迟,连续失败翻倍(默认 500) |
reconnect.maxDelayMs | 两者 | 否 | 退避上限,也是重置重连预算所需上行时间(默认 30000) |
reconnect.maxAttempts | 两者 | 否 | 每次断连连续失败上限(默认 10) |
工具并入注册表
接上后,外部 MCP 工具成为 ctx.tools 上的普通工具。每个工具有两个名字:raw name(tools/call 发给服务器的)与公共名 mcp__<serverName>__<rawName>(模型看到/调用的)。例如 github 的 create_issue 变成 mcp__github__create_issue。公共名规范化到 DeepSeek 函数名契约(≤64 字符、[A-Za-z0-9_-]),改名时追加 (serverName, rawName) 派生的 12 位十六进制哈希防塌名;名字是 (serverName, rawName) 的纯函数,连接顺序、重同步都不改它。
命名冲突是确定性的:两个 server 发同一个 raw 名在各自命名空间下共存;两个存活实例同名 serverName 则后装载的失败;一个 server 清单列两次同名工具视为非法清单;外来注册蹲占本 server 命名空间则整代回滚并大声报错。
并入后它们走同一条工具流水线(见 工具执行):tools/pre-execute 门禁、超时、结果改写都适用,让"允许/拒绝清单"等策略统一作用于外部工具。
连接、重连与失败语义
- 发现:激活时
await listTools(),首轮对话前逐个ctx.tools.register();失败记日志,failOnStartupError: true才拒绝激活。 - 热更新:监听
notifications/tools/list_changed重同步;抓取失败保留上一代工具,注册冲突回滚尝试的那一代。 - 执行:
client.callTool({ name: rawName, arguments }, { signal }),带超时与取消,公共名从不发给服务器;isError走注册表错误路径。 - 掉线重连:监督器按原配置重启、指数退避,成功后重跑发现;恢复这代替换上一代,不重复不泄漏,断连期间最后一代好工具仍注册。同一断连连续失败
maxAttempts次后注销工具并停止重连,直到 HMR 重载或 Host 重启。
与自定义工具的区别
| 本地工具 | MCP 工具 | |
|---|---|---|
| 实现 | 插件 ctx.tools / defineTool | 外部 MCP 服务器 |
| 命名 | snake_case 名 | mcp__<server>__<raw> |
| 生命周期 | 进程内 | 桥接进程/远程 |
| 都可 | 过同一流水线 | 过同一流水线 |
什么时候用
- 已有一堆 MCP 服务器(文件系统、GitHub、数据库、企业内部工具),不想为每个写专用对接
- 想把外部工具纳入 DSH 的工具治理(门禁/超时/取消)之下
- 典型:接 GitHub server 让模型开 issue/查 PR;接数据库 server 暴露只读查询;接企业内部 API 网关,几十个操作一次接入并统一受
tools/pre-execute门禁约束
当前边界
- 只桥接工具:Resources 与 Prompts 没有 harness 消费方。
- 启动超时继承 MCP SDK(60 秒默认),不响应 server 会拖慢激活与清理;streamable HTTP 失败按每次请求体现,不可达是每次调用重试而非被重启。
- 图像是唯一持久富结果桥(rc.7):PNG/JPEG/WebP/GIF 在
ctx.attachments挂载且模型路由显式声明图像输入时,整批解码校验后逐张存为 durable 图像块进入模型上下文;失败/不支持的批次变诊断文本。audio 与 embedded-resource 载荷仍在模型上下文外(执行期规范值保留 JSON),resource 链接只留 name+URI 文本。 - 不支持的 output schema 不强制:
structuredContent回退JsonValue。
验证
# 组合树里看 mcp 客户端
dsh web --dump-config | grep -A3 mcp
# 会话日志里看工具调用(命名 mcp__ 前缀)
zstdcat ~/.dsh/sessions/*/*/session.jsonl.zstd | grep -E '"mcp__' | head