2816 字
约 9 分钟
1
MCP 与 Extensions:外部工具接入的两条路
无标签

DeepSeek Harness · 模型与外部接入 MCP 与 Extensions:外部工具接入的两条路 桥接生态标准与原生扩展怎么分工。核心源码: packages/mcp/mcp-client/ 与 packages/extensions/ 。 一句话速览 桥接生态标准与原生扩展怎么分工

课程目标 读完你能说清三件事:DSH 接外部能力有两条路,MCP 桥负责接协议生态里现成的工具服务器,Extensions 负责让模型在 harness 里现写现跑插件;MCP 桥为什么刻意只桥 tools、工具名怎么用 hash 防碰撞、服务器断线时模型手里的工具会经历什么;以及两条路的信任模型差在哪,一边把风险挡在进程外,一边靠审批和沙箱看管。

交互演示 · 接入方式对照台 先玩再讲。同一个外部能力「查天气」,左边走 MCP 桥,右边走原生 Extension,两条路同时接入。看三件事:工具名怎么生成、服务器断线时模型视角发生什么、两条路的能力面差多少。点「播放」自动走完,或用「单步」逐帧看。

播放 单步 重置

路 A · MCP 桥(外部进程) 世代 G1

外部世界 weather server(尚未启动) 原始工具名 get_forecast (只在网线上出现)

harness 里的 ctx.tools 注册表 公开名 mcp__weather__get_forecast

工具 事件 服务 界面

路 B · 原生 Extension(进程内)

模型的动作 调用 cordis_define ,提交插件源码 等待用户审批:允许这个插件运行吗?

运行中的两半 Host 半: node:vm 沙箱里跑逻辑 Browser 半:页面里渲染天气面板

工具 事件 服务 界面

点「播放」,看同一个能力分别从两条路接进 harness。

逻辑拆解 · 路 A:MCP 桥,把别人的服务器接进来 先解释名词。MCP(Model Context Protocol)是一个开放协议:任何人写一个工具服务器,任何支持 MCP 的客户端都能连上去用它的工具。DSH 的 dsh-mcp-client 插件就是这个协议的客户端,一个插件实例连一个服务器,stdio 子进程和 streamable-http 两种传输都支持。连接成功后它做的事很直白: listTools() 拉一遍工具清单,把每个工具用公开名注册进 ctx.tools ,模型从此把它们当原生工具用。 命名是第一个设计点。每个 MCP 工具有两个名字:原始名只在网线上出现( tools/call 用它),模型看到的公开名是 mcp__服务器名__原始名 。这个格式与 Claude Code 和 Codex 一致,mcp-client 的 README 自己点了这一句。名字必须满足 DeepSeek 函数名约定:最长 64 字符、只允许字母数字下划线连字符。要是替换字符或截断改动了名字,就在尾部追加一个 12 位十六进制的 SHA-256 hash,保证两个不同的工具身份绝不会折叠成同一个名字。整个函数是 (serverName, rawName) 的纯函数:连接顺序、重新同步、别的服务器,都改不了一个工具的名字。

packages/mcp/mcp-client/src/tools.ts 第 96 至 102 行

export function publicToolName(serverName: string, rawName: string): string {
 const joined = `mcp__${serverName}__${rawName}`
 const normalized = joined.replace(INVALID_NAME_CHARS, '_')
 if (normalized === joined && normalized.length <= MAX_PUBLIC_NAME_LENGTH) return normalized
 const hash = createHash('sha256').update(`${serverName}\0${rawName}`).digest('hex').slice(0, HASH_LENGTH)
 return `${normalized.slice(0, MAX_PUBLIC_NAME_LENGTH - HASH_LENGTH - 1)}_${hash}`
}

源码快照说明: 依据本地仓库 deepseek-harness-master,核对文件 packages/mcp/mcp-client/src/tools.ts ,核对日期 2026-08-13。代码块保留源码原文。 第二个设计点是世代(generation)。服务器的工具清单会变,变了就要重新同步。同步分两阶段:先把下一世代的全部工具定义拉完建好,任何一步失败都不碰注册表,上一世代原样活着;拉完了才做交换,先注销旧世代、再注册新世代。 交换阶段的写法值得讲一下。注册循环里,每注册成功一个工具就把它的注销函数存进一张表。任何一次注册抛了冲突(意味着有外来注册霸占了这台服务器的命名空间),catch 分支就把这张表里已注册的全部注销,一个工具都不留,然后记一条 error 日志。注释把意图写得很直白:回滚是为了让模型看到的要么是完整的一个世代,要么什么都没有,绝不能是半套。 出处: packages/mcp/mcp-client/src/tools.ts 第 159 至 172 行的注册与回滚分支,核对日期 2026-08-13。 断线重连也建在世代上。stdio 子进程崩了,supervisor 用指数退避重启它:首次延迟默认 500 毫秒,逐次翻倍,上限 30 秒,一次中断最多试 10 次( README.zh.md 配置表)。中断期间最后一个正常世代保持注册,模型这时调用会失败,但工具名不会凭空消失;重连成功后重新发现,恢复的世代整体替换旧世代,工具既不重复也不泄漏。 serverName 没变的话,新世代的名字逐字相同,KV cache 前缀都保得住。预算也有讲究:连接存活超过 30 秒就重置尝试预算,所以偶尔崩一次的服务器可以无限恢复,反复崩溃循环的服务器最终会耗尽预算被注销,不会永远重启下去。 最后一个设计点最容易被忽略:这座桥刻意只桥了 MCP 的 tools 能力。MCP 协议里还有 resources(资源)和 prompts(提示词模板)两类能力,DSH 一概没接。README 的「已知限制与暂缓事项」一节写得很坦白:

「只桥接 MCP 的工具能力:资源和提示词没有 harness 消费接口,暂缓实现。」 出处: packages/mcp/mcp-client/README.zh.md 第 111 行,核对日期 2026-08-13

逻辑不难还原:harness 内部没有谁会消费一个外部 resource 或外部 prompt,先造桥墩没有意义。工具有明确的消费方(agent loop 的工具调用),所以先桥工具。这属于按需造桥。图片、音频这类非文本结果也做了有损投影,在模型上下文里变成占位符,二进制载荷不进上下文。

逻辑拆解 · 路 B:Extensions,让模型给自己长插件 第二条路完全不同。Cordis 是 DSH 的插件框架,整个 harness 就是一棵 Cordis 插件树。Extensions 子系统让模型在会话里现写一个 Cordis 插件、当场跑起来:写代码前先用 cordis_inspect 查询当前运行时里有哪些服务和接口可用,然后 cordis_define 提交源码, cordis_run 启动,不要了就 cordis_stop 或 cordis_undefine 。这五个工具由 packages/extensions/tool-cordis 注册。 一个动态插件分两半。Host 半在 Node 侧的 node:vm 沙箱里跑逻辑,Browser 半在页面里渲染 UI,两半的生命周期由 ctx.dynamicCordisRunner ( packages/extensions/cordis-host-runner/src/index.ts 第 124 行起)统一管。带 Browser 半的启动要走审批: cordis/request-run 事件把请求送到页面,用户点了允许才继续,还可以勾选一并放行这个插件的后续版本( runHostHalf 的 approveFutureVersions 参数)。每个 Package 版本不可变,改代码就是追加新版本。 把两条路放一起看,它们是正交的,各管一头。MCP 桥面对的是进程外的现成能力,信任模型是隔离:服务器崩了、返回垃圾、断线,都被世代和错误路径挡在桥外,但它能给模型的只有工具这一种东西。Extension 面对的是模型现场生成的代码,跑在自己进程里,能力面大得多:能加工具、能发事件、能注册服务、能画界面,代价是每次运行都在审批和沙箱的看管之下。一个是接外面的电,一个是自己发电。

名字是纯函数 公开名只由 (serverName, rawName) 决定。两个服务器都叫 search 的工具在各自命名空间下共存;连接顺序和重新同步永远不会重命名工具。 世代要么全有要么全无 拉取失败不碰注册表,注册冲突整代回滚。模型看到的永远是完整的一套工具,绝不会是半套。断线期间旧世代保持注册,调用会失败但名字还在。 桥只桥 tools resources 和 prompts 被有意搁置,理由是 harness 里没有它们的消费接口。能力面差距要靠 Extensions 补:工具、事件、服务、界面四样都能加。

横向对比 · 三家怎么接外部能力

Claude Code:MCP 客户端的满配实现 还原源码里的 MCP 实现比 DSH 厚得多:六种传输方式(stdio、sse、sse-ide、http、ws、sdk,见 restored-src/src/services/mcp/types.ts 第 23 至 26 行)、七个配置来源层级(local、user、project、dynamic、enterprise、claudeai、managed)、OAuth 认证加 15 分钟缓存。工具命名和 DSH 同形, mcp__server__tool ,权限规则能精确到工具级或服务器级。还有一个 DSH 没有的防御:工具描述截断到 2048 字符,因为观测到 OpenAPI 自动生成的服务器往描述里塞 15 到 60KB 的文档( services/mcp/client.ts 第 217 至 219 行注释)。资料来源:claude-code-sourcemap-main/study/chapters/08-mcp.md。 差异在取向。Claude Code 把 MCP 当唯一的官方扩展点做深做全;DSH 把 MCP 桥做薄(只桥 tools),把重能力留给原生 Extensions。前者的扩展跑在进程外,后者多给了一条跑在进程内的路。

Grok Build:插件市场路线 Grok Build 仓库里 MCP 客户端( crates/codegen/xai-grok-mcp/ )与插件市场( crates/codegen/xai-grok-plugin-marketplace/ )并存:MCP 负责协议兼容,市场负责分发与信任,走的是集中审核的生态路线。它的 MCP 连接、发现与恢复机制,站内 Grok 专题已经逐行核对过,见 MCP 连接、发现与恢复 ;市场的发现与信任模型见 Plugin Marketplace 的发现与信任 ,这里不重复展开。 三家放一起,光谱就出来了:Grok 靠市场集中管信任,Claude Code 靠七层配置和权限规则分散管信任,DSH 把两条路拆开,各配各的信任模型:桥外隔离,桥内审批。

课堂练习

01 推演一次断线重连的完整时间线 weather 服务器在模型刚拿到工具清单后崩溃,8 秒后被 supervisor 拉起来,这次它的工具清单多了一个 get_alerts 。请按时间顺序推演:崩溃瞬间注册表里有什么?模型在中断期间调用 mcp__weather__get_forecast 会得到什么?重连成功后注册表经历了什么操作, get_forecast 的公开名变了吗?再回答:如果两个不同的服务器 weather 和 weather2 都暴露 get_forecast ,它们会冲突吗,为什么?(提示:世代替换、名字是 (serverName, rawName) 的纯函数。)

Takeaway: MCP 桥接的是别人的能力,Extensions 扩展的是自己的运行时,两条路正交,各配各的信任模型。桥只桥 tools 是刻意的:没有消费方就不造桥墩。工具名是 (serverName, rawName) 的纯函数,世代替换保证模型手里的工具集要么完整要么为空,永远没有中间态。

MCP 与 Extensions:外部工具接入的两条路
http://www.clxhxhhr.top/posts/4035/
作者
clxstart
发布于
2026-09-25
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。