dsh-as-mcp
Exposes a running DeepSeek Harness as an MCP server over Streamable HTTP or a bundled stdio bridge, so an external agent can create workspaces and sessions, prompt, fork, rename and switch the model on them, read and write files, run foreground and background shell commands, call the instance's own
Install
dsh plugin --profile web add dsh-as-mcp
Sandbox test results
| Stage | Result |
|---|---|
| 1 install | ✓ |
| 2 list | ✓ |
| 3 mount | ✓ |
| 4 boot | — |
Four stages run in an isolated sandbox: install (network on) → list → mount → boot (last three with no network).
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)
Rendered from the README published on npm. Formatted only, not edited.
Versions
Published on npm::8,最后更新 2026-11-03