dsh-as-mcp
把正在运行的 DeepSeek Harness 暴露为 MCP server(Streamable HTTP,或附带的 stdio 桥),外部 agent 可创建 workspace 与会话,发起 prompt、fork、重命名、切换模型,读写文件,运行前台与后台 shell 命令,按名调用该实例自身的 agent 工具,并读取单轮与累计 token 用量;装了计价插件时还给出费用回执。
安装
dsh plugin --profile web add dsh-as-mcp
沙箱测试结果
| 阶段 | 结果 |
|---|---|
| 1 安装 | ✓ |
| 2 列装 | ✓ |
| 3 挂载 | ✓ |
| 4 启动 | — |
四阶段在隔离沙箱里跑:安装(有网)→ 列装 → 挂载 → 启动(后三步断网)。
README
<p align="center">
<!-- 故意用绝对 URL:npm 从 tarball 渲染 README,不解析相对图片路径,
写成相对路径在 npm 页面上会是裂图。两个版本是因为 wordmark 是深海军蓝,
在 GitHub 暗色主题下会看不见。 -->
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/xiseliuli/dsh-as-mcp/main/assets/logo-dark.png">
<img src="https://raw.githubusercontent.com/xiseliuli/dsh-as-mcp/main/assets/logo-light.png" width="380" alt="dsh-as-mcp">
</picture>
</p>
<p align="center"><a href="README.md">English</a> | 中文</p>
<p align="center">
<a href="https://github.com/xiseliuli/dsh-as-mcp/actions/workflows/ci.yml"><img src="https://github.com/xiseliuli/dsh-as-mcp/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
<a href="https://github.com/xiseliuli/dsh-as-mcp/releases"><img src="https://img.shields.io/github/v/release/xiseliuli/dsh-as-mcp?label=release&color=2f81f7" alt="Release"></a>
<a href="LICENSE"><img src="https://img.shields.io/github/license/xiseliuli/dsh-as-mcp?color=3fb950" alt="License: MIT"></a>
<a href="https://www.typescriptlang.org/"><img src="https://img.shields.io/badge/TypeScript-5.9-3178C6?logo=typescript&logoColor=white" alt="TypeScript"></a>
<a href="https://nodejs.org/"><img src="https://img.shields.io/badge/node-%5E22.19.0%20%7C%7C%20%3E%3D24-339933?logo=nodedotjs&logoColor=white" alt="Node"></a>
<a href="https://modelcontextprotocol.io/"><img src="https://img.shields.io/badge/MCP-server-8B5CF6" alt="MCP 服务"></a>
<a href="https://github.com/topics/dsh-plugin"><img src="https://img.shields.io/badge/DSH-plugin-4B8BBE" alt="DSH 插件"></a>
</p>
把一个正在运行的 DeepSeek Harness 暴露成 MCP 服务,让别的 agent —— Claude Code、
Codex、另一个 DSH、CI 任务、你自己写的脚本 —— 都能驱动它:创建工作区、新建会话、把编码
任务交给 DSH 的 agent、读写文件、执行命令。
端点跑在 DSH 宿主进程内部,所以通过 MCP 建出来的会话就是真正的 DSH 会话:它实时出现
在 DSH 界面里,跑在 DSH 沙箱中,受和你手敲一样的那套权限策略约束。agent 本身没有任何东西
是被重新实现的。
工作原理
flowchart LR
subgraph clients["任意 MCP 客户端"]
direction TB
c1["Claude Code"]
c2["Codex"]
c3["另一个 DSH"]
c4["CI 任务"]
c5["你自己的脚本"]
end
subgraph host["DSH 宿主进程"]
direction TB
plugin["dsh-as-mcp<br/>(本插件)"]
tools["workspace_* · session_*<br/>file_* · shell_* · dsh_tool_*"]
services["harness 自己的服务<br/>workspace · session · fs · shell"]
plugin --> tools --> services
end
clients -- "Streamable HTTP 或 stdio<br/>bearer token" --> plugin
安装
三种渠道都能把这个 bundle 装进 profile,按你拿到包的方式选一种。
从 npm:
dsh plugin --profile <名称> add dsh-as-mcp
从源码 checkout 里跑,等价命令是 pnpm dsh plugin --profile <名称> add dsh-as-mcp。
从 GitHub:
dsh plugin --profile <名称> add github:xiseliuli/dsh-as-mcp
git 安装拉下来的是源码,不是构建产物 lib/,所以 pnpm 得跑本包的 prepare 脚本
(tsdown && node scripts/build-client.mjs)才能把它建出来。pnpm 的供应链门禁默认拒绝执行
依赖的构建脚本——连带它依赖的 esbuild 的 postinstall 也一并拦下——直到 profile 显式放行,
所以第一次 add 会以 ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED 失败。这是 pnpm 的策略,不是打包
缺陷:git 依赖在安装期可以执行任意代码,所以 pnpm 要求一次精确、明确的放行。
一次放行、按仓库 URL 生效(推荐)。 pnpm 按 <name>@<git url>(不带 #<ref>)匹配 git 依赖的
构建,因此一条条目就能跨 commit、跨版本一直有效。profile 的 pnpm ≥11.19.0 时——DSH 0.2.1-rc.5
自带 11.28.5,成功的那次 add 最后一行会写明用的版本(… using pnpm v…)——把下面这段写进
profile 的 pnpm-workspace.yaml(~/.dsh/profiles/<名称>/pnpm-workspace.yaml),再重跑 add:
allowBuilds: 'dsh-as-mcp@git+https://github.com/xiseliuli/dsh-as-mcp.git': true esbuild: true
裸包名(dsh-as-mcp: true)永远不会放行 git 托管产物:pnpm 认的是带来源的身份,不是包名
(pnpm 文档)。
更早的 pnpm(11.0–11.18)只认精确 key,因为「按仓库 URL 放行 tarball 形式的 github: 依赖」是
pnpm 11.19.0 才有的——本页两条命令
(github:… 和 release .tgz)都是按 tarball 拉取的。失败的那次 add 会打印这个精确 key
(钉在解析出的 commit 上);原样抄过去,如果终端把 URL 折行了就拼回一行;并且此后**每次更新都要
重新放行**(pnpm/pnpm#12367)。不要指望 pnpm 已经替你
写好占位项:这条失败路径下它不会写。pnpm 10 上这个开关叫 onlyBuiltDependencies 而不是
allowBuilds,以报错里给出的 key 为准。
这个放行等于允许在你机器上、在安装阶段执行这个包的构建代码。如果你把依赖钉死到某个 commit
(github:xiseliuli/dsh-as-mcp#<sha>),key 就必须带那个 commit;上面那条仓库 URL 形式放行的是
本仓库的任意 commit。
不想放行构建,就装已经构建好的发布物——两者都带现成的 lib/,都不执行 prepare:
dsh plugin --profile <名称> add dsh-as-mcp dsh plugin --profile <名称> add \ https://github.com/xiseliuli/dsh-as-mcp/releases/download/v<版本>/dsh-as-mcp-<版本>.tgz
git 安装还要求 DSH ≥ 0.1.5-rc.1(见[兼容性](#兼容性));更老的运行时会拒绝加载,除非用
dsh plugin allow-version 授予精确版本豁免。
从 tarball:
pnpm pack dsh plugin --profile <名称> add /绝对路径/dsh-as-mcp-<版本>.tgz
用同一个 tarball 路径重装会悄悄沿用旧构建——原因和规避方法见下文「装进 DSH Desktop」一节,
每次重新打包都换一个文件名。
dsh plugin add 会把包记进 profile 的 dsh.profile.bundles,包内的 cordis.patch.yml
负责提供插件行和默认配置。装完要重启 DSH:bundle patch 是启动时读取的,不热重载。
确认这一层已经合成进去:
dsh --profile <名称> --dump-config | grep -A 30 '# == dsh-as-mcp'
给发布者: GitHub 仓库要打上 dsh-plugin 这个 topic——插件发现就是靠它找到你的仓库;npm
的 latest 标签要指向一个精确的稳定版本(不能是预发布版,也不能是范围),因为
dsh plugin add dsh-as-mcp 解析的就是这个。
让客户端接进来
每个请求都要带 bearer token。解析顺序:
1. 插件配置里的 auth.token;
2. $DSH_HOME/dsh-as-mcp/token;
3. 都没有时在首次运行生成并写入该文件,权限 0600。
默认端点是 http://127.0.0.1:8790/mcp。
无法设置请求头的客户端可以改用 ?token=<token>。但优先用请求头:写在 URL 里的凭证可能被
你的 shell 历史、反向代理的访问日志、或浏览器历史留下。DSH 自己不记录查询串
(webserver/src/index.ts:224),插件也不记录 token,所以这更多是一个值得养成的习惯,而不是
这段代码引入的泄漏。
支持 HTTP 的客户端(Streamable HTTP):
{
"mcpServers": {
"dsh": {
"type": "http",
"url": "http://127.0.0.1:8790/mcp",
"headers": { "Authorization": "Bearer <token>" }
}
}
}
配置面板会生成一段一模一样的配置,并且已经填好当前令牌,所以直接用面板上的复制按钮,
不必手工替换 <token>。
只支持 stdio 的客户端 —— 用随包提供的桥接脚本,它把每条 JSON-RPC 消息通过 HTTP 转给
端点:
{
"mcpServers": {
"dsh": {
"command": "npx",
"args": ["-y", "dsh-as-mcp"],
"env": { "DSH_AS_MCP_TOKEN": "<token>" }
}
}
}
dsh-as-mcp --help 会打印它将使用的端点和 token 状态。桥接脚本只会把从 token 文件读到的
凭证发给回环端点;要把 DSH_AS_MCP_URL 指向远程,必须显式设置 DSH_AS_MCP_TOKEN——复制来的
客户端配置因此无法悄悄把本机凭证外传。「回环」是精确匹配:localhost、::1,或字面量
127.x.x.x 地址,因此 `127.0
…(truncated)
以上内容来自 npm 上的 README,我们只做格式化,不改内容。
版本
npm 上共发布:8,最后更新 2026-11-03