这篇只解决四件事:
- MCP 在 DSH 里怎么接;
- 两种传输方式
stdio和streamable-http怎么选; - 怎么确认 MCP 工具真的出现;
- 怎么通过一次真实调用判断它是否真的可用。
先给结论:MCP 在 DSH 里是什么角色?
从用户视角看,一句话结论:MCP 是连接外部工具的一种标准方式。从当前 DSH 实现看,链路可以画成这样:
所以 MCP Server 本身不是 DeepSeek Harness 插件;但负责连接它的 @deepseek-ai/dsh-mcp-client 是 DSH 插件。
这也是为什么 MCP 配置会和下面这些东西发生关系:
- profile;
- patch;
- 工具注册表。
当前能力边界
当前 DSH MCP Client 只桥接 MCP Tools:
Tools
→ 当前支持
Resources
→ 当前没有 Harness 消费接口
Prompts
→ 当前没有 Harness 消费接口
所以如果一个 MCP Server 的主要卖点是 Resources 或 Prompts,不要自动假设 DSH 当前已经完整支持它的全部 MCP 能力。当前 Learn 教程只围绕 Tools 展开。
第一次测试 MCP,建议选什么 Server?
第一次验证建议选择同时满足这些条件的 MCP Server:
- 工具数量少;
- 不需要生产数据;
- 不会执行危险写操作;
- 能返回一个容易核对的结果。
不要第一次就连接这些系统:
- 生产数据库;
- 公司 CRM;
- 主 GitHub 组织写权限 Token;
- 云服务器管理;
- 支付系统。
连接方式:stdio 与 streamable-http
当前 DSH MCP Client 支持两种 Transport(传输方式):stdio(标准输入输出,本机进程)和 streamable-http(流式 HTTP)。下图先给一个整体对比,再分别展开:
stdio 是什么?
stdio 模式可以先理解成:DSH 在本机启动一个 MCP Server 进程,然后通过标准输入输出和它通信。配置核心字段是 transport、command 和 args:
transport: stdio
command: ...
args: ...一个通用结构示例:
- id: mcp-example
name: '@deepseek-ai/dsh-mcp-client'
config:
serverName: example
transport: stdio
command: node
args: ['/absolute/path/to/server.js']这只是结构示例。真正使用什么 command 和 args,必须以具体 MCP Server 官方说明为准。
stdio 最大的安全边界是什么?
在 stdio 模式下,DSH 会启动你配置的本机可执行程序。例如:
command: npx
args: ['-y', 'some-third-party-server']这意味着本机会执行这个第三方程序。所以不要把「这是 MCP」理解成「它被某个安全容器自动隔离了」。MCP 是连接协议,不是第三方代码安全认证。
streamable-http 是什么?
这种模式不需要 DSH 在本机启动 Server 进程,而是通过 HTTP 连接一个已经运行的 MCP Server:
- id: mcp-example
name: '@deepseek-ai/dsh-mcp-client'
config:
serverName: example
transport: streamable-http
url: https://mcp.example.com/mcp如果需要认证,可以加入请求头:
headers:
Authorization: ...HTTP MCP 的主要风险是什么?
它的风险重点和 stdio 不一样,集中在凭据安全。如果你配置了 Authorization Token、API 密钥、Cookie 或其他 Header,这些都是敏感凭据。
另外要注意:智能体调用 MCP 工具时,相关请求数据会发送给那个外部 MCP Server。使用第三方远程 MCP 前,必须确认数据到底发送到谁那里。
stdio 和 HTTP 怎么选?
可以这样判断:
| 场景 | 更常见选择 |
|---|---|
| MCP Server 就是一个本地 CLI / Node / Python 程序 | stdio |
| Server 已经部署为网络服务 | streamable-http |
| 需要连接公司内部统一 MCP 服务 | streamable-http |
| 本地开发 / 调试 Server | stdio |
不是谁更「高级」,它们只是两种不同的 Transport。
serverName 与工具命名
每个 MCP Server 配置都必须有 serverName,它用于给这个 Server 的工具建立命名空间。当前要求满足 [A-Za-z0-9_-]{1,32},而且在当前存活实例中必须唯一,例如 github、database、crm、browser。
MCP 工具最后叫什么?
假设 serverName 为 github,Server 原始工具名为 create_issue,智能体最终看到的公开工具名通常是 mcp__github__create_issue。当前规则可以记成:
mcp__<serverName>__<rawName>
这样两个不同 MCP Server 都有 search 工具,也不会直接冲突:
mcp__github__search
mcp__docs__search
为什么 serverName 不要随便改?
当前公开工具名称由 serverName 加原始工具名稳定决定。如果 serverName 不变,配置热更新、重新同步或重连后,工具名仍然保持一致。
如果你随意把 github 改成 github-new,模型看到的工具名也会跟着变化。所以更适合一开始就选稳定、可识别的 serverName。
用临时 patch 配置 MCP
在 Learn 阶段可以理解成:把 MCP Client 插件行加入当前 profile 的配置组合。例如 web profile 的用户,可以通过 profile 的 cordis.patch.yml 或临时 --patch 参数添加 MCP Client 配置。
如果你还不理解 profile,先看《DeepSeek Harness Profile 是什么?》(路径 /deepseek-harness/profile/)。
第一次测试为什么推荐临时 patch?
如果只是第一次验证某个 MCP Server,DSHOPC 更建议先用一个独立的临时 patch 文件,例如 mcp-test.patch.yml。好处是:
- 不先污染长期 profile 配置;
- 出问题时容易回退;
- 能清楚知道这次到底增加了什么;
- 验证完再决定是否写进长期配置。
一个 stdio 临时配置示例
创建 mcp-test.patch.yml,结构类似:
- insert:
- id: mcp-example
name: '@deepseek-ai/dsh-mcp-client'
config:
serverName: example
transport: stdio
command: node
args:
- /absolute/path/to/mcp-server.js然后先检查配置组合:
npx @deepseek-ai/dsh --profile web --patch ./mcp-test.patch.yml --dump-config确认 MCP Client 配置行进入了组合,再启动:
npx @deepseek-ai/dsh --profile web --patch ./mcp-test.patch.yml一个 HTTP 临时配置示例
- insert:
- id: mcp-example
name: '@deepseek-ai/dsh-mcp-client'
config:
serverName: example
transport: streamable-http
url: https://mcp.example.com/mcp
headers:
Authorization: !!js '`Bearer ${process.env.MCP_TOKEN}`'!!js 是 DSH 配置支持的写法:在 YAML 里嵌入一段 JavaScript 表达式,启动时求值——这里的作用是把环境变量 MCP_TOKEN 的值拼进请求头,而不是把 Token 本身写进文件。
五层验证:从配置到真实结果
第一步验证:配置组合正确
先确认配置组合正确。运行:
npx @deepseek-ai/dsh --profile web --patch ./mcp-test.patch.yml --dump-config确认这四件事:MCP Client 插件行存在、serverName 正确、Transport 正确、command / URL 没写错。这一步只能证明配置组合正确,不能证明 MCP Server 已经连上。
第二步验证:MCP Client 是否连接成功?
真正启动:
npx @deepseek-ai/dsh --profile web --patch ./mcp-test.patch.yml当前 MCP Client 激活时会依次做三件事:
- 建立连接;
- 执行
listTools(); - 把发现的工具注册进 DSH。
所以启动阶段真正要看的是:有没有成功完成工具发现。
failOnStartupError 是什么?
当前默认值为 failOnStartupError: false。这意味着 MCP 初始连接或工具同步失败时,不一定让整个 DSH profile 启动失败——插件可以继续激活,但没有成功注册工具。
如果你在一个测试环境里希望「MCP 连不上就直接让启动失败」,可以考虑设置 failOnStartupError: true。但普通生产组合是否应该这么设置,要根据 MCP 是否属于关键依赖决定。
第三步验证:工具有没有真的出现?
连接和 listTools() 成功以后,智能体应该能看到对应的 MCP 工具,例如 mcp__example__get_status。这一步验证的是:MCP Server 已经被 DSH 发现,而且工具已经注册。
如果 Server 进程存在,但没有任何工具出现,不能说 MCP 已完整接入。
第四步验证:做一次真实工具调用
这是最关键的一步。不要只停在「工具列表里出现了」。要给智能体一个明确、低风险的任务:调用这个 MCP 工具,获取一个可以独立核对的结果。
例如一个测试 Server 有 get_test_value 工具,就让它返回一个约定值,如 MCP-VERIFY-7A91。最终验证的是这条完整链路:
工具被发现
↓
Agent 发起真实调用
↓
Server 收到请求
↓
返回正确结果
↓
Agent 使用该结果
只有这一步完成,才能说 MCP 最小功能链路成功。
为什么「连接成功」也不等于功能正常?
MCP 可以失败在很多层。例如:
Server 进程能启动
≠
MCP initialize 成功
initialize 成功
≠
tools/list 成功
tools/list 成功
≠
工具调用成功
工具调用成功
≠
外部真实业务操作正确
所以 DSHOPC 对 MCP 也采用分层验证。
MCP 建议使用五层验证
- 配置进入 profile;
- Client 建立连接;
- 工具成功发现;
- 真实工具调用成功;
- 外部结果真实可核对。
第五步验证:核对真实外部结果
如果是一个会写外部系统的 MCP,还应该额外验证写入后的真实状态,例如创建一条测试记录后重新读取,确认记录确实存在。不要只相信工具返回的 "success": true。
连接生命周期:超时、重连与热更新
当前每次 MCP callTool 默认超时为 60000 ms,也就是 60 秒。可以通过 toolCallTimeoutMs 配置:
toolCallTimeoutMs: 60000如果一个正常工具确实需要更长时间,可以按 Server 特性调整。但不要遇到失败第一反应就把超时改成无限大。应该先确认:
- Server 是否真的在工作;
- 网络是否正常;
- 工具是不是卡住;
- 外部 API 是否响应。
MCP 会自动重连吗?
当前默认开启自动重连(reconnect.enabled: true),也就是连接丢失后会尝试恢复。默认参数为:
initialDelayMs = 500
maxDelayMs = 30000
maxAttempts = 10
失败后使用指数退避(exponential backoff,每次重试的间隔逐渐拉长)。
自动重连是不是永远不会断?
不是。达到 reconnect.maxAttempts 之后,当前 Server 的工具会被注销,重连停止,直到 HMR(Hot Module Replacement,热更新)重新加载或重新启动 Host。
stdio Server 崩了会怎样?
当前 supervisor 会尝试按照原始配置重新启动 Server。恢复以后会再次发现工具,并用新一代工具集合替换旧集合。如果工具列表没有变化,工具名保持稳定。
HTTP Server 不可达时也完全一样吗?
不完全一样。当前官方说明:Streamable HTTP 的失败更多通过每次请求以及 MCP SDK 自身的 SSE(Server-Sent Events,服务器推送事件)恢复机制暴露。它不像 stdio 子进程崩溃以后重新 spawn 一个本地进程那样简单。所以排错时要先知道自己用的是哪种 Transport。
MCP 配置能热更新吗?
当前 MCP Client 支持 HMR。编辑它的配置项后,会执行断开加重新连接。如果 serverName 不变,重新同步后对应工具名称保持稳定。
因此只修改 MCP Client 配置时,当前实现不要求一律重启整个 DSH 进程。但如果你同时改变的是 profile Bundle 成员,那仍然属于插件 / Bundle 生命周期问题,需要按插件篇处理。
凭据与安全边界
stdio 场景下,从环境变量读取:
env:
SOME_TOKEN: !!js process.env.SOME_TOKENHTTP 场景下,同样从环境变量读取:
headers:
Authorization: !!js '`Bearer ${process.env.MCP_TOKEN}`'外部权限与上下文成本
MCP 工具能不能写外部系统?可以。是否能写,取决于 MCP Server 暴露了什么工具,以及你的凭据有什么权限。
例如一个 GitHub MCP 可能不仅能读 Issue,也可能能创建 Issue、修改仓库;数据库 MCP 可能不仅能查询,也可能执行写操作。
DSH 的权限能完全保护外部 MCP 操作吗?
不要这样假设。DSH 自己的权限、审批、沙箱是重要控制边界,但 MCP Tool 的最终外部权限还取决于 Server 实现、Token 权限和外部系统授权。
MCP 是安全认证吗?
不是。这是整篇最重要的一句话之一:MCP 是连接协议,不是安全认证。
一个 Server「支持 MCP」,只能说明它使用这套协议,不能因此推导出下面任何一条:
- 代码安全;
- 不会泄露数据;
- 不会执行危险操作;
- 不会保存 Token;
- 工具描述一定真实;
- 外部服务一定可信。
MCP 工具数量多了会有什么影响?
每个已发现的 MCP 工具都会带工具名、描述和输入 schema(工具参数的结构定义)进入模型工具目录。所以工具越多,每次请求的工具 schema token 成本也可能越高。
当前工具重新同步会替换工具集合,而不是不断累积重复工具。但如果一个 MCP Server 暴露几十上百个复杂工具,仍然会增加模型工具选择负担。因此,「能接很多 MCP」不等于「越多越好」。
MCP 返回图片怎么办?
当前 DSH 对 MCP 丰富结果有额外能力判断,并不是所有返回类型都会自动进入模型上下文。对普通 MCP 接入,先验证文本与结构化工具结果即可。图片、附件和丰富结果的内部桥接细节更适合放到 Architecture。
为什么 Resources / Prompts 暂时不要写进教程主线?
因为当前官方明确:DSH MCP Client 只桥接 Tools。所以本文不应该为了「讲完整 MCP 协议」而大量介绍 Resources 和 Prompts——那会让用户误以为当前 DSH 已经消费这些能力。
MCP 协议本身的完整架构,更适合放 Architecture。
排错
遇到问题时,建议按层检查,从配置到功能逐层缩小范围。这六层与上面五层验证一一对应,只是把 Server 层和认证层单独拆出来。
1. 配置层
npx @deepseek-ai/dsh --profile web --patch ./mcp-test.patch.yml --dump-config确认这几件事:
- 插件行存在;
- Transport 正确;
serverName正确;command/ URL 没写错。
2. Server 层
stdio:command 本身能不能启动?HTTP:URL 是否真的可达?
3. 认证层
确认这三件事:
- Token 是否存在;
- 环境变量是否传入;
- 权限是否足够。
4. MCP 协议层
确认 initialize / tools/list 是否成功。
5. 工具层
确认工具是否注册进 DSH。
6. 功能层
确认真实 callTool 是否成功。
怎样算 MCP 真正接通?
至少应该完成下面全部检查项:
- MCP Client 配置进入当前 profile;
- Server 可以启动 / 网络可以连接;
- 初始工具发现成功;
- 对应
mcp__<serverName>__...工具出现; - 智能体发起一次真实工具调用;
- 工具调用没有超时;
- 返回结果可以独立核对。
如果是写操作,还要加上两条:使用测试环境;写入后再次读取真实外部状态确认成功。做到这里,MCP 最小功能链路才算验证完成。
下一步
到这里,你已经学完三条主要扩展能力的定位:技能给 Agent 方法,MCP 接外部工具,插件深度扩展 Harness。
如果 MCP 当前连不上,按《DeepSeek Harness 常见问题与报错排查》(路径 /deepseek-harness/troubleshooting/)处理。
如果你想研究 MCP Client 为什么最终注册进 ctx.tools、Tool Schema 怎样进入模型请求、MCP 工具怎样经过工具流水线、Server 生命周期与 Cordis 的关系,这些应该进入 Architecture。
如果你准备测试某个具体 MCP Server,安装、连接、读写、兼容性、稳定性和版本结果应该进入 Lab。