Model Context Protocol(MCP)让 dsh 无需为每个服务器编写适配器即可与外部工具服务器通信。在 dsh 中这是一个单一包,@deepseek-ai/dsh-mcp-client(packages/mcp/mcp-client),一个桥接插件:连接一个 MCP 服务器、发现服务器的工具,并把每个工具以服务器限定的名字注册为原生 dsh 工具。每台 MCP 服务器一个插件实例,在 cordis.yml 中配置。
| 字段 | 版本 / 角色 |
|---|---|
packages/mcp/mcp-client | MCP 客户端桥 |
官方参考见 packages/mcp/mcp-client/README.md;没有独立的子系统页面,因此本页直接取材自该 README 与 packages/mcp/mcp-client/src/*。
MCP 工具如何浮现
每个 MCP 工具都有两个名字:
- 原始 MCP 名——在 wire 上的
tools/call中发送; - 公开名
mcp__<serverName>__<rawName>——注册在ctx.tools上,也是模型所见。
公开名被规范化为 DeepSeek 函数名契约(64 字符,[A-Za-z0-9_-]);当规范化改变名字时,会追加 (serverName, rawName) 的确定性 12 位十六进制哈希,使不同工具绝不合二为一。名字是 (serverName, rawName) 的纯函数——连接顺序、重新同步、其他服务器都不会改名。这正是 Claude Code 与 Codex 所用的形态,例如 mcp__github__create_issue、mcp__web__search。
连接时,插件激活会 await listTools() 并在组合启动首轮之前通过 ctx.tools.register() 注册每个广告的工具。在工具注册表中的生命周期:每个 MCP 工具都是普通的 ToolDefinition,其 execute 以超时 + 中止支持调用 client.callTool({ name: rawName, arguments }, { signal })——公开名绝不发给服务器。
传输与配置
两种传输。两者都经由 Model Context Protocol 生成/发起来自真实 MCP 客户端的连接:
- 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' }
- 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 / env / cwd | stdio | 否 | 不经 shell 传参;净化后的环境 + env 覆盖;工作目录 |
url | http | 是 | MCP 服务器 URL |
headers | http | 否 | 附加 HTTP 头(如认证 token) |
toolCallTimeoutMs | 两者 | 否 | 每次 callTool 超时(默认 60000) |
failOnStartupError | 两者 | 否 | 初始连接/同步失败时拒绝激活(默认 false) |
reconnect.enabled / initialDelayMs / maxDelayMs / maxAttempts | 两者 | 否 | 指数退避重连:500 → 30000 ms 上限,10 次尝试 |
stdio 启动会故意过滤环境:名称通常标识凭据的环境变量及所有 DSH_* 变量在启动前被移除;其他环境变量保持继承。
生命周期、重新同步与重连
- 发现:初始
listTools()门控激活;失败会记日志,且除非failOnStartupError,否则插件以with no tools激活。 - 重新同步:客户端监听
notifications/tools/list_changed并重新同步。抓取阶段失败会保留上一代注册;注册冲突会回滚整代且不留下该服务器的任何工具。 - 重连:断开/崩溃时,监督器以指数退避(
initialDelayMs翻倍至maxDelayMs)重启原服务器配置,并在成功后重新发现——恢复的一代替换之前的一代,因此工具既不重复也不泄漏。中断期间最后一代良好注册保持存活(调用直到恢复前都失败)。重连按代/outage 预算:连失maxAttempts次后该服务器工具被注销,重连停止,直到 HMR 重载或 Host 重启;存活超过maxDelayMs的连接会重置预算。 - HMR:编辑条目会触发断开 + 重连,无需进程重启;
serverName不变则复现出相同的工具名。
面向模型的行为
- 工具结果:规范成功值为
{ content: JsonValue[], structuredContent? };完整 JSON 块对程序化调用方存活。当广告的outputSchema受支持时,structuredContent会据其校验;不支持的 schema 词汇回退为无约束的JsonValue。isError经由注册表错误路径拒绝该调用。 - 原生文本投影:文本块以换行连接;图像、音频、资源与不支持块变为短占位符(其完整 JSON 块仍留在执行本地值中)。参数与映射文本保留到压缩为止;二进制/资源负载被丢弃而非加入上下文。
- KV 缓存:当发现的集合/schema 不变时前缀稳定;改变某工具的重新同步会替换定义,并可能从首个被改变的 schema 起使复用失效。恢复不变列表则保持前缀稳定。
示例:examples/mcp-memory
仓库在 examples/mcp-memory/ 下随附三个默认关闭的参考配置,通过本客户端把第三方内存服务器接到 dsh:
| 文件 | 系统 | 传输 |
|---|---|---|
memorix.cordis.yml | Memorix | stdio |
mcp-reference-memory.cordis.yml | MCP Reference Memory | stdio |
engram.cordis.yml | Engram | stdio |
用 dsh web --patch 'examples/…/<name>.cordis.yml' 启用其一。dsh 解析 Cordis overlay、启动 stdio 命令(或连接 URL)、发现工具并把它们暴露为 mcp__<serverName>__<tool>。dsh 不下载服务器、初始化其数据库、选择模型,也不监督独立的 HTTP 服务;对 stdio,通用客户端随 dsh 插件生命周期启动并停止子进程。
已知局限
- 工具是唯一被桥接的 MCP 能力——Resources 与 Prompts 没有 harness 消费方,被推迟。
- 启动超时继承自 MCP SDK——dsh 不暴露自己的连接/发现超时;每次 initialize 或分页
tools/list用 SDK 的 60 秒默认值。 - 重连在传输关闭时触发——HTTP 失败经由 SDK 的 SSE 流恢复按请求浮现,而非被监督器重生。
- 原生非文本渲染有损,且不支持的 MCP 输出 schema 不被强制(回退为
JsonValue)。
延伸阅读
- 工具注册表与执行流水线——已注册 MCP 工具运行之处
- 设置与配置——
cordis.yml/cordis.patch.yml如何配置插件 - SDK 协议——面向程序化调用方的
mcp__<server>__<tool>命名规范化 packages/mcp/mcp-client/README.md——桥接插件的完整行为与配置packages/mcp/mcp-client/src/connection.ts——连接监督器与重连examples/mcp-memory/README.md——三个内存服务器参考配置