DeepSeek Harness 源码解析 · 第 20 讲
DeepSeek Harness 源码解析系列 第 20 讲:MCP(Model Context Protocol)集成 基于 DeepSeek Harness 源码 · 2026-09-04 💡 本讲一句话: MCP server 是"别人写的工具供应商",DeepSeek Harness 用一个约 930 行的客户端插件把它接进来——serverName 命名空间防冲突、指数退避 + 稳定性窗口防崩溃循环、两阶段原子换表保证模型永远看到完整工具集。读完你能看懂任何 MCP 集成里最容易出事故的三件事:重连、命名、信任边界。 一、插件入口:一个实例 = 一台 MCP server @deepseek-ai/dsh-mcp-client 的定位写在包描述里:「connects to MCP servers and registers their tools on ctx.tools」。它不是服务(service),而是一个 命名空间插件 ——只有具名导出 name / inject / Config / apply ,没有默认导出。每个插件实例只连一台 server;要接多台就在 cordis.yml 里加载多个实例。 先看仓库里的真实用法( examples/mcp-memory/engram.cordis.yml ,第 3-11 行): 📄 examples/mcp-memory/engram.cordis.yml (第 3-11 行) - insert: # cordis.yml 的插入式组合:把下面这个插件实例挂进当前 agent 配置 - id: memory-engram # 实例 id,HMR/诊断时用来定位"是哪一台 server" name: '@deepseek-ai/dsh-mcp-client' # 指向 mcp-client 包;一个实例只服务一台 MCP server config: # 该实例的连接配置块:命名空间 + stdio 传输参数 serverName: engram # 本地命名空间:模型看到的工具名会是 mcp__engram__<原始名>,必须全局唯一 transport: stdio # 传输方式选子进程 stdio(另一种是 streamable-http) command: engram # 要启动的可执行文件——MCP server 本身是个独立进程 args: [mcp] # 直接传参、不经过 shell 插值,避免注入面 cwd: !!js process.cwd() # cordis.yml 允许 !!js 标签:工作目录取宿主当前目录 为什么这样设计: 把"一台 server = 一个插件实例"而不是"一个插件管 N 台 server",是刻意的。Cordis 的生命周期(effect、dispose、HMR)都是按实例走的——实例化即连接、销毁即断开,命名空间预留/释放天然跟着 effect 走。如果做成多 server 管理器,一台 server 的重连状态机就会和插件生命周期纠缠在一起,而本讲后面会看到重连逻辑本身已经足够复杂。 入口 apply() 做了四件事:校验重连配置、预留命名空间、启动连接监督器、阻塞激活直到首次同步完成( packages/mcp/mcp-client/src/index.ts ,第 140-181 行): 📄 packages/mcp/mcp-client/src/index.ts (第 140-181 行) export async function apply(ctx: Context, config: Config): Promise<void> { // 插件入口;显式保持 async——Cordis 会把"带原型的普通函数"当构造函数,其返回的 Promise 不算启动工作 const reconnect = resolveReconnectPolicy(config.reconnect, `mcp-client(${config.serverName}): reconnect`) // 加载期就 fail loud:重连配置写错(含绕过 schema 的程序化构造)直接拒绝本实例,而不是等运行时才炸 ctx.effect(()…