让外部智能体通过 MCP 调用 DSH 会话与工具
29.6/100
综合分
0
GitHub 星标
—
npm 周下载
—/5
用户评分
数据截至 2026/10/27 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add ahhTou/dsh-mcp-bridge该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)
状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态信任档位:仅索引
图例:✓ 通过! 需注意— 未判定 / 无数据· 「未判定」= 本站尚无数据,不代表插件有问题
档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码v2.7.0🔵 扫描:含敏感能力尚无人工评估展开查看扫描说明与证据 ↓
安全扫描自动 · 静态分析源码
v2.7.0静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。
读写/删除本地文件
查看扫描证据(共 9 处,展示前 9 条)
- 写入本地文件lib/bridge.js:1030
await mkdir(workspace, { recursive: true }); - 写入本地文件lib/bridge.js:1034
await mkdir(root, { recursive: true }); - 读取本地文件lib/bridge.js:3171
return await realpath(value); - 写入本地文件lib/endpoint.js:106
await mkdir(directory, { recursive: true, mode: 0o700 }); - 写入本地文件lib/endpoint.js:109
await writeFile(temp, `${JSON.stringify(payload, null, 2)}\n`, { mode: 0o600 }); - 写入本地文件lib/endpoint.js:110
await rename(temp, file); - 删除本地文件lib/endpoint.js:112
await rm(temp, { force: true }).catch(() => {}); - 读取本地文件lib/endpoint.js:128
current = JSON.parse(await readFile(file, 'utf8')); - 删除本地文件lib/endpoint.js:135
await rm(file, { force: true });
扫描于 2026/11/4(0 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库
静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
把 DeepSeek Harness 的会话能力变成 MCP Server:Claude Code、Cursor、Codex 等 Agent 可创建会话、发送消息、长轮询回合、回答提问,复用 DSH 的模型、工具与工作区。
README
由 DeepSeek 最新模型翻译生成DSH MCP Bridge
把 DeepSeek Harness(DSH) 的会话能力包装成 MCP Server,让 Claude Code、Cursor、VS Code、Codex 或其他智能体通过 MCP 调用 DSH:
- 在已有工作区或指定目录新建会话(dsh_list_workspaces 先选工作区)
- 查询会话状态:dsh_get_status 一行摘要 + 实时活动,dsh_get_session 完整状态
- 查询最近一个回合的结果:dsh_get_turn_result 返回 finish_reason、error(408 单独标出)、usage、tool_calls、duration_ms
- 长轮询:dsh_send_message { wait: false } 拿到 since,再用 dsh_wait 等完成/提问/错误,不用每 10 秒轮询
- 查询历史消息
- 选择会话模式(DSH agent preset),MCP 新建会话默认标准模式
- 切换模型、切换推理强度(reasoning effort)
- 调整权限等级(sandbox + approval preset),MCP 新建会话默认完全权限
- 发送对话并等待回复
- 终止正在执行的对话
- 归档会话
展开完整文档(README 剩余 1 节)
DSH MCP Bridge
把 DeepSeek Harness(DSH) 的会话能力包装成 MCP Server,让 Claude Code、Cursor、VS Code、Codex 或其他智能体通过 MCP 调用 DSH:
- 在已有工作区或指定目录新建会话(dsh_list_workspaces 先选工作区)
- 查询会话状态:dsh_get_status 一行摘要 + 实时活动,dsh_get_session 完整状态
- 查询最近一个回合的结果:dsh_get_turn_result 返回 finish_reason、error(408 单独标出)、usage、tool_calls、duration_ms
- 长轮询:dsh_send_message { wait: false } 拿到 since,再用 dsh_wait 等完成/提问/错误,不用每 10 秒轮询
- 查询历史消息
- 选择会话模式(DSH agent preset),MCP 新建会话默认标准模式
- 切换模型、切换推理强度(reasoning effort)
- 调整权限等级(sandbox + approval preset),MCP 新建会话默认完全权限
- 发送对话并等待回复
- 终止正在执行的对话
- 归档会话
插件以 Host-only bundle 的形式安装在 DSH profile 中,MCP 端点复用 DSH Web GUI 的 HTTP 监听端口,不需要额外进程或端口。
端口会变。 DSH 每次启动可能分配到不同端口。插件启动时会把当前地址写到固定文件 ~/.dsh/mcp-bridge-endpoint.json(可配置 endpoint_file),脚本应优先读这个文件,不要写死端口或每次 lsof。
0.8.0 本轮新增
- 客户端绑定与自动收尾:initialize 返回 Mcp-Session-Id;客户端在后续请求带上它,插件就知道会话属于谁。断连 30 秒或心跳超过 120 秒没有请求时,自动清空队列并取消该客户端仍在跑的回合;也可以用 dsh_list_client_sessions / dsh_stop_client_sessions 手动查看和收尾。
- 不登记工作区 / 清理工作区:dsh_create_session.register_workspace=false 不再往工作区列表加项;dsh_archive_session.remove_workspace=true 和 dsh_remove_workspace 可清理空工作区。
- 多目录 workspace-write:dsh_create_session.writable_roots 把沙箱根设为 workspace 与这些目录的共同父目录(DSH 只支持一个 workspace root,因此不是精确白名单,见下文)。
- 稳定模型别名:gpt/luna、gpt/sol、deepseek/flash 等别名不再绑定 provider 名;旧的 旧provider/gpt-6-luna 也能在 provider 改名后解析。找不到时返回近似候选。
- 模型能力与提供方健康度:dsh_list_models 增加 accepts_images、context_window、max_output_tokens、alias,以及最近一小时的 provider_health(turns/failures/retries/error_rate)。
- 按消息调推理强度:dsh_send_message.reasoning_effort 只对本次回合生效,回合结束后恢复原选择;mode=queue 在已有回合运行时会被拒绝,避免污染正在跑的回合。
- 归档后可读:dsh_get_messages / dsh_get_turn_result / dsh_get_status 支持已归档会话,并在消息结果里附 last_turn(含 408 等错误)。
1. 可行性结论
已在本机 DSH Desktop 上端到端验证通过:
| 能力 | 使用的 DSH 服务 | 验证结果 |
| 指定工作区新建会话 | workspaceRegistry.list/create + sessionController.create | ✅ 可按 workspace_id 复用已有工作区,或按路径自动注册新目录 |
| 查询会话状态 | sessionController.list/projections/page、agents、sessionQuery | ✅ 运行状态、模式、模型、队列、最近消息;dsh_get_status 提供一行摘要 |
| 查询回合结果 | durable turn/start/turn/end/tool/* 事件 + sessionController.inspect | ✅ finish_reason、408 error.http_status、usage、工具数、真实耗时;dsh_get_turn_result 在 send 超时后仍可用 |
| 长轮询 | sessionController.follow + 提问通知 | ✅ turn_end / question / error / timeout;dsh_wait 不空转 |
| 固定端点发现 | webServer.getOrigin + 原子写文件 | ✅ ~/.dsh/mcp-bridge-endpoint.json,无需每次 lsof |
| 选择/切换会话模式 | agentPresets.remoteExportList/resolve/select | ✅ 新建默认 standard;首回合前可切换,之后锁定 |
| 切换模型 | sessionController.modelCatalog + selectModel | ✅ 跨 provider 切换 |
| 切换推理强度 | 同上,reasoningEffort | ✅ off/low/high/max 等按模型校验 |
| 调整权限等级 | permissionPresets.catalog/current/resolve/set + approval.setPolicy | ✅ sandbox + approval 一起切换;MCP 新建会话默认 danger-full-access |
| 发送对话 | sessionController.prompt + follow | ✅ 等待回合结束并返回最终回复 |
| 终止对话 | sessionController.cancel | ✅ 运行中的回合被取消,会话回到 idle |
| 归档会话 | workspaceRegistry.archiveSession | ✅ 从活动列表隐藏,日志保留 |
关键点:DSH 的 ctx.sessionController 已经提供了创建、prompt、取消、模型选择、历史读取和事件流的完整 Host API;插件只需要把这些 API 映射成 MCP 工具,并注册一个 HTTP 路由。不需要修改 DSH 核心,也不需要启动第二个服务。
权限默认值也已实测:MCP 新建会话的 permission 为 danger-full-access,Agent 在 runtime context 中回报 Current DSH file policy: danger-full-access,并提示审批已禁用。
模式默认值同样已实测:MCP 新建会话的 mode 为 standard;dsh_list_modes 返回当前 DSH 的完整模式列表,dsh_set_mode 可在首回合前切换,首回合后会返回 mode_locked。
2. 架构
Other Agent / MCP Client
│ Streamable HTTP (JSON-RPC 2.0)
▼
http://127.0.0.1:<port>/mcp ← 端口见 ~/.dsh/mcp-bridge-endpoint.json │ ▼
@local/dsh-mcp-bridge (Host plugin)
│
├─ sessionController.create / prompt / cancel / selectModel / page / follow
├─ agents.get / inbox
├─ sessionQuery.filterSessions
├─ workspaceRegistry.create / list / archiveSession
├─ agentPresets.remoteExportList / resolve / select
└─ permissionPresets.catalog/current/resolve/set + approval.setPolicy
│
▼
DeepSeek Harness session runtime
- MCP 传输:Streamable HTTP,POST /mcp,JSON 响应;不提供 SSE 长连接。
- MCP 客户端身份:initialize 会返回 Mcp-Session-Id;后续请求带上它就能绑定会话、做租约和自动收尾。不带这个头的客户端仍按无状态方式工作,只是没有归属能力。
- 工具结果统一放在 content[0].text 的 JSON 文本中;工具级失败通过 isError: true 返回。
- 发送消息时通过 sessionController.follow 订阅 durable 事件,识别本次 rpcId 对应的用户消息,然后等待该回合的 turn/end,收集最后的 assistant 文本。
- dsh_get_turn_result 从 durable 日志重建最近一个 turn/start … turn/end:finish_reason、error.http_status、usage、工具调用数、真实回合耗时。即使 dsh_send_message 已超时/断连,结果仍在。
- dsh_get_status 的「最近事件」是最后一条 durable 事件的 time(回合中途会变),并额外给出当前工具、本回合工具调用次数、队列消息来源和 last_turn。
- dsh_wait 同样走 follow,但只在回合结束、出现提问、出错或超时时返回;完成与失败都以正常结果返回,不靠 isError。
- 插件把当前端点写入 endpoint_file(默认 ~/.dsh/mcp-bridge-endpoint.json),并在 /mcp/info 的 endpoint_file 字段中回显路径。
- MCP 新建会话默认使用 standard 模式(DSH agent preset);调用方可用 dsh_create_session.mode 选择 standard、ptc、minimal、cordis 等已声明模式。
- MCP 新建会话默认使用 danger-full-access 权限预设(完全文件访问、无审批);这是本桥接的显式默认,不改变 DSH GUI 新会话的默认预设。调用方可用 dsh_create_session.permission 或 dsh_set_permission 覆盖。
3. 安装
本 profile(已安装)
当前 desktop profile 已经安装了本 bundle,行 id 为 include:dsh-mcp-bridge,配置文件:
~/.dsh/profiles/desktop/cordis.patch.yml 中的 dsh-mcp-bridge 行。
其他 profile
把本目录通过 DSH 的插件管理器安装(推荐,不要手写 profile 的 package.json):
plugin_manager install_bundle target=/absolute/path/to/dsh-mcp-bridge
安装后重启或让 DSH 重新加载该 bundle。本插件是 Host-only,不需要构建步骤,没有 npm 依赖。
修改后重新加载
本插件通过软链接安装到 profile。Desktop profile 默认启用 @deepseek-ai/dsh-hmr(base 组合包里的 hmr 条目,root: []),改完源码后通常不需要重启 DSH:让 ~/.dsh/profiles/desktop/cordis.patch.yml 的内容变化一次(单纯 touch 无效),或在侧边栏插件页把 dsh-mcp-bridge 关闭再打开,HMR 会重新组合并重启文件已变化的插件。也可以配置 hmr.root 监听本工作区,之后保存源码即自动重载。
完整触发方式、机制、实测记录、限制与排错见 不重启 DSH 热重载插件(HMR)。
如果 hmr 条目被禁用或不可用(非 Desktop / headless profile),只能完全重启 DSH Desktop。
4. 连接 MCP
端点与固定文件
MCP: http://127.0.0.1:<port>/mcp
Info: http://127.0.0.1:<port>/mcp/info
端口取自 DSH Web GUI 的实际监听端口,每次启动可能不同。插件启动后会原子写入:
~/.dsh/mcp-bridge-endpoint.json
示例内容(不含 token):
{
"schema_version": 1,
"server": "dsh-mcp-bridge",
"version": "0.8.0",
"url": "http://127.0.0.1:57685/mcp",
"endpoint": "http://127.0.0.1:57685/mcp",
"info_endpoint": "http://127.0.0.1:57685/mcp/info",
"origin": "http://127.0.0.1:57685",
"host": "127.0.0.1",
"port": 57685,
"path": "/mcp",
"auth": "loopback-only",
"token_required": false,
"pid": 12345,
"started_at": "2026-10-23T08:00:00.000Z",
"updated_at": "2026-10-23T08:00:00.000Z"
}
脚本读取:
ENDPOINT=$(jq -r .endpoint ~/.dsh/mcp-bridge-endpoint.json)
PORT=$(jq -r .port ~/.dsh/mcp-bridge-endpoint.json)
curl -s "$ENDPOINT/info"
- 文件在插件启动时写入,权限 0600;干净退出时删除。进程崩溃时可能留下旧文件,可先检查 pid 是否存活,或用 /mcp/info 验证。
- auth 为 bearer 时只表示需要 token,文件里不会写 token 本身。
- 也可以配置绝对路径:
- id: dsh-mcp-bridge
name: '@local/dsh-mcp-bridge'
config:
path: /mcp
endpoint_file: /Users/me/.dsh/dsh-endpoint.json
- 如果希望端口本身固定,可在 profile 的 cordis.patch.yml 里覆盖 webserver.port(例如 57685)。这会改变 DSH GUI 的监听端口;端口冲突时 DSH 启动失败,因此默认仍建议用 endpoint 文件。
Claude Code
ENDPOINT=$(jq -r .endpoint ~/.dsh/mcp-bridge-endpoint.json)
claude mcp add --transport http dsh "$ENDPOINT"
Cursor(.cursor/mcp.json)
把 url 换成 endpoint 文件里的 endpoint:
{
"mcpServers": {
"dsh": {
"url": "http://127.0.0.1:<port>/mcp"
}
}
}
VS Code(.vscode/mcp.json)
{
"servers": {
"dsh": {
"type": "http",
"url": "http://127.0.0.1:<port>/mcp"
}
}
}
通用 Streamable HTTP 配置
{
"mcpServers": {
"dsh": {
"type": "http",
"url": "http://127.0.0.1:<port>/mcp"
}
}
}
curl 自检
ENDPOINT=$(jq -r .endpoint ~/.dsh/mcp-bridge-endpoint.json)
curl -s "$ENDPOINT/info"
curl -s -X POST "$ENDPOINT" \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","clientInfo":{"name":"curl","version":"0"}}}'
5. 工具
| 工具 | 作用 | 主要参数 |
| dsh_create_session | 在已有/指定工作区创建会话;可跳过工作区登记、放宽 workspace-write 根 | workspace(绝对路径)或 workspace_id(二选一,必需)、mode、model、provider、reasoning_effort、permission、register_workspace(默认 true)、writable_roots(需 workspace) |
| dsh_get_session | 查询状态、模式、模型、权限、队列内容、最近消息、最近回合结果 | session_id、include_messages、message_limit |
| dsh_get_status | 实时状态:一行摘要 + 最后事件时间、当前工具、本回合工具数、队列来源、last_turn | session_id |
| dsh_get_turn_result | 取最近(或指定)回合的完整结果,失败也作为正常数据返回 | session_id、turn(可选) |
| dsh_get_questions | 查询待回答的问题批次、选项和可回答性 | session_id |
| dsh_answer_question | 提交结构化答案,继续前台工具或补答超时问题 | session_id、call_id、answers |
| dsh_list_sessions | 列出活动会话 | workspace、limit、include_blank |
| dsh_list_workspaces | 列出已注册工作区(id、路径、会话数) | 无 |
| dsh_list_client_sessions | 列出当前 MCP 客户端创建/接管的会话,含 running/queued | only_running、include_archived、limit |
| dsh_stop_client_sessions | 清空并停止当前客户端拥有的运行中/排队会话 | 无 |
| dsh_list_models | 模型列表:effort、稳定别名、看图/上下文能力、提供方错误率 | 无 |
| dsh_set_model | 切换模型和/或推理强度 | session_id、model、provider、reasoning_effort |
| dsh_list_modes | 列出会话模式(agent preset)、默认模式 | 无 |
| dsh_set_mode | 切换空白会话的模式 | session_id、mode |
| dsh_list_permissions | 列出可选权限预设、sandbox/approval 与默认值 | 无 |
| dsh_set_permission | 切换权限等级(sandbox + approval) | session_id、permission |
| dsh_send_message | 发送消息,默认等待回复;wait: false 立即返回 since;可覆盖本回合 effort | session_id、message、mode、wait、timeout_seconds、reasoning_effort |
| dsh_wait | 长轮询:完成、提问、出错、超时或空闲时返回 | session_id、since、request_id(队列消息用)、timeout_seconds |
| dsh_stop_session | 终止当前回合 | session_id |
| dsh_get_messages | 读取最近对话消息;归档会话可读,附 last_turn | session_id、limit、before_seq |
| dsh_archive_session | 归档会话(日志保留、仍可读),可顺手删空工作区 | session_id、remove_workspace |
| dsh_remove_workspace | 删除工作区登记(默认要求没有非归档会话) | workspace_id、only_if_empty |
典型流程
dsh_list_workspaces / dsh_list_models / dsh_list_modes / dsh_list_permissions
↓ 选工作区(workspace_id)、provider/model/effort、会话模式、权限预设
dsh_create_session { workspace_id: "workspace-...", mode: "standard", model: "deepseek-flash", reasoning_effort: "max", permission: "danger-full-access" }
↓ 得到 session_id(也可以传 workspace: "/abs/path" 自动注册新目录)
dsh_send_message { session_id, message: "帮我检查这个仓库", wait: false }
↓ 立即返回 accepted + since
dsh_wait { session_id, since, timeout_seconds: 300 }
↓ 回合结束 / 提问 / 错误 / 超时时返回;把 cursor 作为下一次 since
dsh_get_turn_result { session_id } ← 即使 send 早已超时,也能拿到最近回合结果
↓
dsh_get_status / dsh_get_session / dsh_get_messages(需要细节时) ↓ dsh_set_mode(首回合前)/ dsh_set_permission(运行中调整权限等级) ↓ dsh_stop_session(需要中止时) ↓ dsh_archive_session(清理,可选)
dsh_create_session
会话必须有一个工作目录。两种指定方式二选一:
{
"workspace_id": "workspace-...",
"mode": "standard",
"model": "deepseek-flash",
"provider": "deepseek-account",
"reasoning_effort": "max",
"permission": "danger-full-access"
}
- workspace_id:来自 dsh_list_workspaces,复用已注册的工作区,不创建/注册任何目录。
- workspace:绝对路径;目录不存在时递归创建,并注册为 DSH workspace。
两者都不传返回 workspace_required,都传返回 invalid_params,workspace_id 不存在返回 workspace_not_found。
model / provider / reasoning_effort 可省略:省略 model 时用 DSH 默认模型;同一个模型 id 存在于多个 provider 下时(例如 deepseek-flash),必须同时传 provider,否则返回 model_ambiguous;reasoning_effort 按所选模型的 reasoning.efforts 校验,省略时用模型默认值。provider 不能单独传。
mode 可省略,默认 standard。它是 DSH 的 agent preset:决定该会话挂载的工具、prompt 区块和 skill 组合。dsh_list_modes 返回当前部署可用的模式;agent_preset 是旧调用方的别名,二者只能传一个(或传相同值)。
permission 可省略,默认 danger-full-access。完整权限预设由 dsh_list_permissions 返回;read-only、workspace-write、danger-full-access 是否可用,取决于当前 DSH 配置。
返回:
{
"session_id": "session-...",
"workspace_id": "...",
"workspace": "/Users/me/project",
"sandbox_root": "/Users/me/project",
"writable_roots": [],
"registered_workspace": true,
"sandbox_scope": "workspace",
"mode": "standard",
"agent_preset": "standard",
"model": {
"provider": "deepseek-account",
"model": "deepseek-flash",
"reasoning_effort": "max"
},
"permission": {
"preset": "danger-full-access",
"sandbox": "danger-full-access",
"approval": "never"
}
}
- 用 workspace 时,目录默认会注册为 DSH workspace;用 workspace_id 时,会话 attach 到该工作区,不会新建目录。
- mode 会在创建前校验;非法或损坏的模式不会留下空会话。
- model / reasoning_effort 会先在模型目录中校验,写错不会留下空会话;model 可写稳定别名(见 dsh_list_models),例如 gpt/luna。
- permission 会在创建后、首条 prompt 前应用;非法预设会在创建前被拒绝。
不登记工作区(0.8.0)
dsh_create_session 每调用一次就把目录注册成工作区,会让 dsh_list_workspaces 越来越长。传 register_workspace: false:
{
"workspace": "/Users/me/project",
"register_workspace": false,
"mode": "standard"
}
- 目录仍会被创建并作为会话 cwd,但不会出现在工作区列表;返回 workspace_id: null、registered_workspace: false。
- 想清掉之前自动登记的空工作区:dsh_remove_workspace { workspace_id, only_if_empty: true },或归档时用 dsh_archive_session { remove_workspace: true }。
多目录 workspace-write(0.8.0)
DSH 的 workspace-write 只有一个 workspaceRoot,没有「额外白名单」设置。插件的做法是把沙箱根设为 workspace 与 writable_roots 的共同父目录:
{
"workspace": "/Users/me/Dev/repo-a",
"writable_roots": ["/Users/me/Dev/repo-b"],
"permission": "workspace-write",
"register_workspace": false
}
返回会带:
{
"workspace": "/Users/me/Dev/repo-a",
"sandbox_root": "/Users/me/Dev",
"writable_roots": ["/Users/me/Dev/repo-b"],
"sandbox_scope": "common_ancestor",
"registered_workspace": false
}
- 这两个仓库都能写,但同属 /Users/me/Dev 的其他目录也会变成可写。这是共同父目录,不是精确的 per-root 白名单;要更严格,只能把仓库放到同一个专用父目录,或继续用 danger-full-access 自行约束。
- 会话的 cwd 会变成这个共同父目录,workspace 字段仍返回原来的工作目录。依赖仓库内 AGENTS.md 的任务,可能需要在 prompt 里明确写「先 working_directory 到 <workspace>」,否则初始指令发现会从共同父目录开始。
- writable_roots 需要 workspace 路径,不能和 workspace_id 一起用;默认 register_workspace 为 false。
- 共同父目录是 /(例如跨磁盘、跨卷)时返回 common_root_unavailable。
dsh_list_workspaces
列出已注册的 DSH 工作区,供 dsh_create_session.workspace_id 选择:
{
"workspaces": [
{
"workspace_id": "workspace-...",
"path": "/Users/me/project",
"title": "project",
"session_count": 3
}
],
"total": 1
}
- session_count 不含已归档会话。
- path 可以直接作为 dsh_list_sessions.workspace 的过滤条件。
- 列表为空时,仍可给 dsh_create_session.workspace 传一个路径,创建并注册第一个工作区。
dsh_list_modes
返回当前 DSH 声明的模式、profile 默认模式,以及本桥接的 MCP 默认模式:
{
"default_mode": "standard",
"mcp_default": "standard",
"modes": [
{
"id": "standard",
"name": "standard",
"description": "…",json "is_default": true, "available": true }, { "id": "ptc", "name": "ptc", "is_default": false, "available": true } ], "note": "…"
}
- default_mode 是 DSH 配置档当前的默认值(用户可能在 GUI 里改过)。
- mcp_default 是 dsh_create_session 的默认值,固定为 standard。
- available: false 表示该 preset 当前挂载失败,broken 会给出原因;不要用它创建会话。
dsh_set_mode
json
{ "session_id": "session-...", "mode": "ptc" }
返回: json { "session_id": "session-...", "previous": "standard", "selected": "ptc", "locked": false, "note": "The mode applies to this blank session. DSH locks mode selection after the first turn." }
- 只在会话首回合开始前可用。DSH 会在 turn/start 后锁定模式;此时调用返回 isError: true,code 为 mode_locked。
- 需要换了模式再对话时,请新建会话,并在 dsh_create_session.mode 中指定。
- 切换会重新挂载该 preset 的工具/prompt/技能组合,空会话的所有后续回合都使用新模式。
dsh_send_message
默认 wait: true,等回合结束后返回: json { "accepted": true, "pending": false, "session_id": "session-...", "request_id": "...", "message_seen": true, "turn": 1, "finish_reason": "completed", "stopped": false, "error": null, "reply": "最终回复文本", "assistant_messages": 1, "tool_calls": 2, "duration_ms": 12345, "waited_ms": 12400, "since": 10, "cursor": 42, "last_seq": 42,
"usage": {
"input_tokens": 1234,
"output_tokens": 456,
"total_tokens": 1690
}
}
- wait: false:立即返回 accepted: true, pending: true, request_id, since, cursor。把 since 传给 dsh_wait,不要自己 sleep 轮询;如果消息可能排在正在运行的回合后面,再传上 request_id。
- timeout_seconds 超时:返回 pending: true, timed_out: true, message_seen, waited_seconds, since, cursor,回合继续运行;dsh_wait { since } 可以在它结束时立刻拿到结果。若此时有前台提问,还会返回 waiting_for_answer: true 和 pending_questions。
- mode: "queue":排队到下一个回合;mode: "steer":尝试注入当前运行中的回合。
- reasoning_effort(0.8.0):只对本次消息启动的回合生效,回合结束后自动恢复原选择。机械活可以传 low/off;mode=queue 在已有回合运行时会被拒绝(session_busy),避免改到正在跑的回合;mode=steer 会影响该运行回合的下一次模型请求。同一会话同时只能有一个待恢复的 effort override。底层仍走 selectModel,所以会短暂写入 DSH 的「新会话默认模型」,回合结束后恢复。
- 如果回合以 error / blocked 结束,dsh_send_message 仍按 MCP 错误返回(isError: true),错误 JSON 的 details 中带 finish_reason、error(含 http_status)和旧的 failure 字段。轮询/长轮询路径请用 dsh_get_turn_result 或 dsh_wait,它们把失败当正常数据返回。
- 支持长任务:timeout_seconds 最大 600;更长的任务用 wait: false + dsh_wait(timeout_seconds 同样可以到 600,客户端 HTTP 超时要更大)。
finish_reason 的完整取值(工具说明里也已列出):
| 值 | 含义 |
| completed | 正常结束 |
| error | 模型/传输错误。error.message、error.code、error.status、error.http_status;408 断流会得到 http_status: 408 |
| aborted | 被取消(abort_reason.kind 给出 user/parent/hook/disposed/legacy) |
| blocked | 在发出模型请求前被阻塞(例如会话已归档) |
| max-tokens | 达到输出 token 上限 |
| interrupted | 回合被中断 |
| forked | 会话分叉导致回合结束 |
- 回合仍在运行、等待回答或等待超时时,没有 finish_reason;看 pending、waiting_for_answer 或 dsh_wait.event。
- finish_reason 只表示回合怎么结束,不表示任务是否完成;任务完成与否仍要看回复/验证文件。
dsh_send_message 的返回怎么读:
| pending | timed_out | waiting_for_answer | finish_reason | 含义 |
| false | false | false | completed/error/… | 回合已结束,看 finish_reason |
| true | true | true | 无 | 子代理在提问;先 dsh_get_questions + dsh_answer_question,再 dsh_wait |
| true | true | false | 无 | 还在跑;用 dsh_wait { since }(可带 request_id) |
| true | false | 任意 | 无 | 事件流结束但没有 turn/end;用 dsh_get_turn_result 兜底 |
dsh_get_turn_result
从 durable 日志重建最近一个回合(或 turn 指定的回合),不依赖 dsh_send_message 是否还在等。失败回合作为正常结果返回: json { "session_id": "session-...", "turn": 1, "open": false, "running": false, "finish_reason": "error", "ok": false, "outcome": "failure",
"error": {
"message": "408: ",
"code": "INVALID_REQUEST",
"status": 408,
"http_status": 408
},
"abort_reason": null,
"usage": {
"input_tokens": 65731,
"output_tokens": 4622,
"total_tokens": 919889,
"cache_read_tokens": 849536
},
"tool_calls": 49,
"tool_errors": 1,
"assistant_messages": 19,
"duration_ms": 364009,
"started_at": "2026-10-23T06:15:37.959Z",
"ended_at": "2026-10-23T06:21:41.968Z",
"last_event_at": "2026-10-23T06:21:41.968Z",
"last_seq": 205,
"start_seq": 8,
"end_seq": 205,
"request_id": "540b8d0d-...",
"reply": "最后一段 assistant 文本(若有)", "interrupted": false,
"active_tools": [],
"waiting_for_answer": false,
"pending_question_count": 0,
"open_question_count": 0
}
- ok:只有 completed 为 true;error/blocked/max-tokens/aborted/interrupted/forked 为 false;运行中或没有回合为 null。
- outcome:success / failure / stopped / incomplete / running / none。
- error.status 是有效的 HTTP 状态:服务商直接给了就用它,否则从 "408: …" 这类消息头解析;error.http_status 是同一值的显式别名。两者在无法判定时都为 null。
- duration_ms 是真实回合的 turn/start → turn/end 时间,不是 HTTP 等待时间。
- 回合仍在运行时:open: true, running: true, finish_reason: null,active_tools 给出当前工具和已运行时长,usage/tool_calls 是到目前为止的累计值。
- turn 可选;指定不存在的回合返回 isError: true, code: "turn_not_found"。
- reply 是最后一段非空 assistant 文本,最多 40000 字符;长回复用 dsh_get_messages 取全文。
- 没有回合的空白会话返回 turn: null, outcome: "none",不报错。
dsh_wait
长轮询,不空转。since 用 dsh_send_message 返回的 since,或上一次 dsh_wait 返回的 cursor: json { "session_id": "session-...", "event": "turn_end", "pending": false, "timed_out": false, "status": "idle", "since": 10, "cursor": 42, "turn": 1, "finish_reason": "completed", "ok": true, "outcome": "success", "error": null,
"usage": { "input_tokens": 1234, "output_tokens": 456 },
"tool_calls": 2,
"duration_ms": 12345,
"last_turn": { "turn": 1, "finish_reason": "completed", "ok": true }
}
event 取值:
| 值 | 含义 | pending |
| turn_end | 回合结束。顶层字段就是 dsh_get_turn_result 的结果(含失败时的 error.http_status: 408) | false |
| question | 有前台问题等待回答;用 dsh_get_questions / dsh_answer_question,然后带 cursor 再 dsh_wait | true |
| timeout | 到达 timeout_seconds 仍在运行;用返回的 cursor 重试 | true |
| idle | 智能体空闲、队列为空,没有可等的事件 | false |
| cold | 没有活智能体;发一条消息才会激活 | false |
| stream_end | 事件流意外结束,且没有正在等待回答的问题;用 dsh_get_turn_result 兜底 | true |
- 省略 since:从当前最后事件之后开始等;要取最近一个已完成回合的结果,用 dsh_get_turn_result(since: -1 会从日志中最早的 turn/end 开始匹配,适合新建会话的第一个回合)。
- 队列消息(mode: "queue")可能排在正在运行的回合后面。 这时把 dsh_send_message 返回的 request_id 也传给 dsh_wait;它只认这个提示词所在的回合,不会把前一个回合的结束误当成本次完成。
- 返回的 cursor 永远是下次 since 的推荐值。
- dsh_wait 不会因为回合失败而返回 isError;只有参数/session 错误才走 MCP 错误。
- timeout_seconds 1–600,默认 120;客户端的 HTTP 超时要大于它。
dsh_get_status
实时状态接口:不读消息、不查模型目录,但会读 durable 事件以计算真实活动: json { "session_id": "session-...", "status": "running", "summary": "运行中 · standard · deepseek-flash (max) · 队列 1(下一回合 1;来源 dsh_send_message×1) · 正在执行 bash(12 秒) · 本回合工具 3 次 · 最近事件 2 秒前", "running": true, "waiting_for_answer": false, "pending_question_count": 0, "open_question_count": 0, "last_event_at": "2026-10-23T09:00:02.000Z", "last_event_type": "tool/call",
"activity": {
"turn": 2,
"open": true,
"running": true,
"started_at": "2026-10-23T08:59:50.000Z",
"elapsed_ms": 12000,
"tool_calls": 3,
"tool_errors": 0,
"current_tool": "bash",
"active_tools": [
{ "call_id": "call_...", "name": "bash", "started_at": "2026-10-23T08:59:50.000Z", "seq": 123, "elapsed_ms": 12000 }
],
"last_event_at": "2026-10-23T09:00:02.000Z",
"last_event_type": "tool/call",
"last_event_age_ms": 2000
},
"queue": {
"next_turn": 1,
"next_step": 0,
"total": 1,
"messages": [
{
"target": "next_turn",
"message_id": "...",
"source": "user",
"request_id": "...",
"via": "dsh_send_message",
"sent_at": "2026-10-23T08:59:49.000Z",
"mode": "queue",
"preview": "排队消息的文本…"
}
]
},
"last_turn": {
"turn": 1,
"open": false,
"finish_reason": "error",
"ok": false,
"outcome": "failure",
"error": { "message": "408: ...", "code": "INVALID_REQUEST", "status": 408, "http_status": 408 },
"usage": { "input_tokens": 65731, "output_tokens": 4622 },
"tool_calls": 49,
"tool_errors": 1,
"duration_ms": 364009,
"ended_at": "2026-10-23T06:21:41.968Z",
"request_id": "..."
}
}
- status:running / idle / cold(没有活智能体,DSH 需要时再加载)/ waiting_for_answer(前台问题正在等待回答)。
- status: "idle" 不等于成功。 回合超时、408 断流、取消之后都会回到 idle;必须看 last_turn.ok / last_turn.finish_reason / last_turn.error.http_status,或调用 dsh_get_turn_result。
- last_event_at 是最后一条 durable 事件的真实时间,回合中途会随工具调用/assistant 消息更新;不再是「本回合开始时间」。
- activity.current_tool 只在回合打开时给出;active_tools 支持并行工具,elapsed_ms 是工具已运行时长。回合结束后 active_tools 为空,tool_calls 仍是上一回合的总数。
- queue.messages 给出排队消息的 target(next_turn / next_step,即下一回合 / 本回合补话)、来源、request_id、preview,以及是否来自桥接的 dsh_send_message;摘要里会压缩成 来源 tool-jobs×1 / dsh_send_message×2。
- 若问题已超时进入 continued,pending_question_count 会包含它,但 open_question_count/waiting_for_answer 只统计仍在等回答的 open;智能体的真实运行状态不会被误报。
- 归档会话返回 archived: true,摘要追加“已归档”;仍可读 last_turn,但不能再发消息。
- 需要 workspace、权限、turn boundary 或消息时,用 dsh_get_session。
客户端绑定、租约与自动收尾(0.8.0)
MCP 本身是无状态的,所以插件用 MCP 标准的 Mcp-Session-Id 识别客户端:
- 客户端 initialize 后,响应头会带 Mcp-Session-Id。后续每个请求都带上它,插件会把 dsh_create_session 创建的会话记到这个客户端名下,并在客户端最后一次变更操作时更新归属。
- 你现有的 Python 驱动(dsh_mcp.py)已经会自动保存并回传这个头,不需要改代码。
- 不发送这个头的旧客户端仍然可用,只是没有归属/自动收尾能力,dsh_list_client_sessions 会返回 client_identity_required。
自动收尾规则(默认值):
| 情况 | 默认行为 |
| 请求连接在响应完成前断开 | 等 30 秒宽限(client_disconnect_grace_seconds);期间没有新请求就收尾 |
| 客户端超过 120 秒没有任何请求 | 视为心跳超时(client_lease_seconds);有长请求在飞时不算超时 |
| 收尾动作 | 清空该会话的 next-turn/next-step 队列,然后对运行中的回合调用 cancel;不归档、不删除日志 |
| 客户端正常退出但不发 DELETE | 同上,等租约超时后收尾 |
text
dsh_list_client_sessions { only_running: true }
→ 当前客户端的会话、running/queued、last_turn
dsh_stop_client_sessions {}
→ 立即清空并停止当前客户端仍在跑/排队的会话 DELETE /mcp (Mcp-Session-Id: ...) → 立即关闭 client 并触发同样的收尾
- 想关闭自动收尾:auto_stop_owned_sessions: false;想调长/调短心跳:client_lease_seconds(0 表示不限)。
- 会话归属是“最后一个对会话做变更操作的客户端”。只读工具不会抢归属;新进程要继续旧会话时,先调用 dsh_send_message 或 dsh_stop_session 即接管。
- 自动收尾后会话仍在 DSH 列表里,可以用 dsh_get_turn_result / dsh_get_messages 查它最后发生了什么。
提问状态、查询与回答(0.8.0) text
dsh_get_status { session_id }
→ status: "waiting_for_answer"
→ open_question_count: 1
dsh_wait { session_id, since }
→ event: "question", waiting_for_answer: true
dsh_get_questions { session_id }
→ 见下面的返回示例
dsh_answer_question {
session_id,
call_id: "call_00_qSnAwky21Knthj6mc1aW5615",
answers: [{ id: "choice", selected: ["选项 A"] }]
}
→ accepted: true, delivery: "tool_result"
dsh_get_questions 的完整返回示例(pending_questions 与 batches 是同一个数组的别名): json { "session_id": "session-...", "waiting_for_answer": true, "count": 1,
"pending_questions": [
{
"call_id": "call_00_qSnAwky21Knthj6mc1aW5615",
"state": "open",
"answerable": true,
"questions": [
{
"id": "add_status_tool",
"header": "确认需求", "question": "要我加一个轻量状态工具吗?", "detail": "可选的补充说明",
"options": [
{
"id": "新增 dsh_get_status(推荐)", "index": 0, "label": "新增 dsh_get_status(推荐)", "description": "选项说明" }, { "id": "先不改", "index": 1, "label": "先不改" } ], "multi_select": false } ] } ],
"batches": [ "…与 pending_questions 相同…" ]
}
- 一组问题 = 一次 ask_user_question 工具调用;pending_questions/batches 的每个元素是一个批次。call_id 是不透明字符串(通常是 call_00_...;桥接合成的 legacy 问题可能是 mcp-question-<uuid>),原样传给 dsh_answer_question.call_id。
- 选项没有单独的 ID 字段:options[].id 是 label 的别名,selected 里放 label(也就是这个 id)。index 只用于展示。
- selected 可以为空的情况:
- 显式跳过:{ "id": "q1", "selected": [] }(不传 custom)。
- 自由文本:{ "id": "q1", "selected": [], "custom": "回答" }。
- 多选 + 自由文本:selected 可以同时有选项,并带 custom。
- 单选:最多一个选项标签,或一个非空 custom,二者不能同时出现。
- state:open(仍在前台等待,MCP/GUI 先答者生效)或 continued(限时问题已超时或由日志修复恢复,仍可补答,delivery: "steer")。
- answerable: false:该前台问题未被桥接捕获(例如桥接加载前的旧问题);只能在 DSH GUI 回答。归档会话不接受答案。
- 每次必须完整提交这组所有题目,id 各出现一次。重复排队返回 question_reply_queued;已失效问题返回 question_not_found。
- dsh_get_session 也返回 pending_questions;dsh_list_sessions 返回 pending_question_count 和同样的等待状态。
- 恢复边界:MCP 请求断开不会取消原提问;重新查询后可继续回答。普通 legacy 阻塞式提问只在 Host 内存中捕获,桥接加载前的旧问题、Host 重启后已取消的问题不能凭空恢复;只有 DSH 持久化并保留为 continued 的问题支持冷会话加载后补答。重新发一条普通消息不等于回答旧问题。
- 不处理权限审批和计划审核:带 intent 的问题不交给 MCP 回答器;审批仍走 DSH 原来的审批流程。调用方必须提交用户明确提供的答案,不能将等待或超时理解为许可。
- GUI/MCP 并行回答的取消机制依赖当前 DSH 的 Cordis waterfall 和 gateway 共享请求对象的行为;升级 DSH 后应回归验证。插件不修改 DSH 核心,不接管无 Agent 或子 Agent 的提问。
dsh_get_messages
返回的 messages 只包含真正的对话消息(user/assistant)。DSH 注入的运行时上下文、技能目录、时间提示等 user 消息会被省略,避免污染其他 Agent 的上下文。
0.8.0 起:
- 归档会话也能读:archived: true,durable 日志保留;不能再 dsh_send_message,但可以读消息和回合结果。
- 返回值增加 last_turn(与 dsh_get_status.last_turn 同形),包含 finish_reason、ok、error.http_status、usage、tool_calls、duration_ms。408 的完整 error.message 就在这里,不用只靠界面上的一句半截回复。
json
{
"session_id": "session-...",
"messages": [
{ "role": "user", "seq": 10, "time": "2026-10-22T13:52:22.000Z", "text": "...", "request_id": "..." },
{ "role": "assistant", "seq": 20, "time": "2026-10-22T13:52:23.000Z", "text": "..." }
],
"has_more": false,
"next_before_seq": null
}
分页:把上一页的 next_before_seq 作为下一次的 before_seq。
dsh_list_models(0.8.0)
除了 provider/model/effort,现在还返回稳定别名、能力和最近的提供方健康度: json {
"default": { "provider": "deepseek-account", "model": "deepseek-flash", "reasoning_effort": "high" },
"providers": [
{
"provider": "cli-proxy",
"name": "本地 CLI Proxy",
"models": [
{
"id": "gpt-6-luna",
"name": "gpt-6-luna",
"alias": "gpt/luna",
"input_modalities": ["text"],
"accepts_images": false,
"context_window": 262144,
"max_output_tokens": 32768,
"capabilities_available": true,
"reasoning": { "efforts": [{ "id": "low", "name": "Low" }, { "id": "max", "name": "Max" }], "default_effort": null }
}
]
}
],
"provider_health": {
"cli-proxy": {
"window_seconds": 3600,
"turns": 12,
"failures": 4,
"retries": 2,
"error_rate": 0.333,
"retry_rate": 0.167,
"state": "degraded",
"last_error_at": "2026-10-23T09:00:00.000Z",
"last_error_code": "INVALID_REQUEST",
"last_error_http_status": 408,
"last_error_message": "408: stream disconnected…"
}
},
"health_scope": {
"observed_since": "2026-10-23T08:00:00.000Z",
"window_seconds": 3600,
"note": "Observed by this bridge process from durable request/header, llm/retry and turn/end events; not historical telemetry."
}
}
- alias 是去掉版本号后的稳定名:gpt-6.1-sol 和 gpt-6-sol 都是 gpt/sol,gpt-6-luna/gpt-5.6-luna 都是 gpt/luna。多个模型共享别名时,解析会优先当前会话 provider、再默认 provider,然后选版本更高的。
- accepts_images:true/false 来自 provider 披露的 inputModalities;null 表示 provider 没有披露,不要把它当成“能看图”。
- context_window / max_output_tokens:provider 披露的上下文上限和默认输出上限;null 表示未知。
- provider_health 是本 bridge 进程加载后观察到的一小时滚动窗口:turns 是带模型请求的回合数,failures 是 turn/end 为 error 的次数,retries 来自 llm/retry 事件;error_rate = failures / turns,state 为 ok/degraded/unstable,没有样本时为 unknown/turns: 0。它不包含 DSH 启动前或其他实例的历史,只适合判断“最近是不是在反复 408”。
- capabilities_available:false 表示 provider 的 resolveModelInfo 失败,capability_error 给出原因;别名和 effort 仍然可用。
dsh_set_model
json
{ "session_id": "session-...", "model": "deepseek-v4-pro", "provider": "deepseek-official", "reasoning_effort": "low" }
规则:
- model:模型 id、精确 name,或稳定别名(gpt/luna、gpt/sol、deepseek/flash);用 dsh_list_models 获取。旧的 旧provider/gpt-6-luna 也能在 provider 改名后解析:provider 前缀不存在时会被忽略,只按模型名匹配。
- provider:当同一个模型 id 存在于多个 provider 下时用于消歧;如果只切 effort,可省略。
- 选不到时返回 model_not_found,details 里带 available_models 和 suggestions(按共享模型族 token 排序的前几个候选);重名多 provider 时返回 model_ambiguous 和 candidates。
- 只传 reasoning_effort:保持当前模型,只换强度。
- 只传 model:新模型使用其默认 effort。
- 同时传两者:一起切换。
- effort 会按该模型的 reasoning.efforts 校验,非法值会返回可用列表。
- 注意:DSH 的 sessionController.selectModel 会把选择同时保存为“新会话的默认模型”。这是 DSH GUI 的既有行为,不是本插件额外引入的。
dsh_list_permissions
返回当前部署可选的权限预设、它们的 sandbox/approval 组合以及默认预设: json { "default_preset": "workspace-write", "mcp_default": "danger-full-access",
"options": [
{ "value": "read-only", "name": "read-only" },
{ "value": "workspace-write", "name": "workspace-write" },
{ "value": "danger-full-access", "name": "danger-full-access" }
],
"default_options": [
{ "value": "read-only", "name": "read-only" },
{ "value": "workspace-write", "name": "workspace-write" },
{ "value": "danger-full-access", "name": "danger-full-access" }
]
}
default_options 是配置中可持久选择的预设;auto(若当前集成存在)只出现在 options 中,custom 是派生状态,二者不能通过 dsh_set_permission 选择。mcp_default 是 dsh_create_session 的默认权限(danger-full-access);default_preset 是 DSH GUI 新会话的全局默认,二者可能不同。
dsh_set_permission
json
{ "session_id": "session-...", "permission": "workspace-write" }
返回: json { "session_id": "session-...",
"previous": {
"preset": "danger-full-access",
"sandbox": "danger-full-access",
"approval": "never"
},
"selected": {
"preset": "workspace-write",
"sandbox": "workspace-write",
"approval": "ask"
},
"notified": true,
"note": "Applies to subsequent tool calls..."
}
- 权限预设同时决定 sandbox mode 和 approval policy:
- danger-full-access → sandbox danger-full-access,approval never
- workspace-write → sandbox workspace-write,approval ask
- read-only → sandbox read-only(若部署配置了该预设)
- 切换立即写入 session log,影响后续工具调用。
- 如果会话已有回合且 approval policy 发生变化,正在运行的 Agent 会收到一条“approval policy changed”的用户消息(notified: true);空白会话只记录 durable 变更,不额外注入消息。
- 只改当前会话,不改 DSH 全局默认权限;GUI 新建会话的默认权限仍由 DSH 的 defaultPreset 决定。
- MCP 新建会话默认已经是 danger-full-access,通常只有在需要收紧权限时才调用本工具。
dsh_stop_session
- 会话未运行:返回 stopped: false, status: "idle"。
- 会话运行中:调用 sessionController.cancel,返回 stopped: true, status: "cancelling"。
- 只取消当前回合,不会丢弃队列中的 pending 消息。
- 用 dsh_get_session 确认 running 变为 false。
dsh_archive_session / dsh_remove_workspace
归档会话,停止其运行中的工作,并从活动列表隐藏;durable 日志保留,可在 DSH GUI 中 unarchive。用于其他 Agent 完成任务后的清理。
- 0.8.0 起归档会话仍然可用 dsh_get_messages / dsh_get_turn_result / dsh_get_status 读回,last_turn 会保留 408 等错误;只是不能再发新消息。
- remove_workspace: true:归档后如果该工作区没有其他非归档会话,顺手删除工作区登记。共享工作区还有别的会话时不会删,返回 workspace_remaining_sessions。
- dsh_remove_workspace { workspace_id, only_if_empty: true }:单独清理空工作区;默认有活跃会话时返回 workspace_not_empty,传 only_if_empty: false 可强制删除登记(不会删会话/日志)。
6. 安全
默认完全权限:MCP 调用 dsh_create_session 创建的会话默认使用 danger-full-access(完全文件访问、审批策略 never)。这意味着被调用的 Agent 可以读写本机任意可访问路径,不需要审批。只应把 MCP 端点暴露给受信任的本机 Agent;不要把端口暴露到公网。
DSH 的 webServer 明确不提供 server-wide auth,路由 owner 自己负责请求策略。本插件因此实现了自己的准入规则:
1. 默认只接受 loopback 请求(127.0.0.1 / ::1 / ::ffff:127.0.0.1)。DSH 默认也只绑定 loopback。
2. 如果配置了 token,则所有 /mcp 和 /mcp/info 请求必须携带:
http
Authorization: Bearer <token>
3. 有 Origin 头的浏览器请求必须是 DSH 自身的 origin;其他 origin 返回 403。没有 CORS 头,浏览器跨域请求会被拒绝。
4. 请求体上限 8 MiB;非 application/json 返回 415。
5. 如果 DSH 被配置为绑定非 loopback 地址,而插件没有设置 token,非 loopback 请求会被拒绝(403)。
6. 客户端租约/自动收尾只操作 Mcp-Session-Id 对应的归属会话:清空其队列并 cancel 运行中的回合,不归档、不删除日志;Mcp-Session-Id 不是认证凭据,真正的访问控制仍依赖 loopback/token。
需要更保守的默认时,可在 dsh_create_session 中显式传 permission: "workspace-write",或在创建后调用 dsh_set_permission。
设置 token
在 profile 的 cordis.patch.yml 中修改本 bundle 行: yaml
- id: dsh-mcp-bridge
name: '@local/dsh-mcp-bridge'
config:
path: /mcp
token: 'replace-with-a-random-token-at-least-16-chars'
然后重新加载插件。MCP 客户端配置需要加 header,例如 Claude Code: bash
ENDPOINT=$(jq -r .endpoint ~/.dsh/mcp-bridge-endpoint.json)
claude mcp add --transport http dsh "$ENDPOINT" \
--header "Authorization: Bearer replace-with-a-random-token-at-least-16-chars"
不要把 DSH 的 Web 端口暴露到公网。本插件的 token 只保护 /mcp,不保护 DSH GUI 的其他路由。
7. 配置 yaml
- insert:
- id: dsh-mcp-bridge
name: '@local/dsh-mcp-bridge'
config:
path: /mcp
token: 'optional, >=16 chars'
endpoint_file: '~/.dsh/mcp-bridge-endpoint.json'
client_lease_seconds: 120
client_disconnect_grace_seconds: 30
auto_stop_owned_sessions: true
| 字段 | 默认 | 说明 |
| path | /mcp | MCP 路由前缀;info 路由为 ${path}/info。不能是 / |
| token | 未设置 | Bearer token;设置后所有请求必须携带。至少 16 字符 |
| endpoint_file | ~/.dsh/mcp-bridge-endpoint.json | 固定位置的端点文件;支持 ~、绝对路径和相对路径。文件权限 0600,不含 token |
| client_lease_seconds | 120 | 客户端超过这么久没有任何请求视为心跳超时;0 表示不限时。长请求在飞时不计超时 |
| client_disconnect_grace_seconds | 30 | 请求连接异常断开后的宽限;期间没有新请求就收尾 |
| auto_stop_owned_sessions | true | 超时/断连时是否自动清队列并 cancel 该客户端拥有的运行中回合 |
修改 path 后,端点变为 http://127.0.0.1:<port>${path},endpoint 文件会同步更新。
8. 限制与注意事项
- 只支持 Streamable HTTP:没有 stdio、没有旧版 HTTP+SSE、没有 Mcp-Session-Id。Claude Code、Cursor、VS Code 等现代 MCP 客户端可用。
- 默认模式 standard:dsh_create_session 显式使用 standard,即使 DSH profile 的默认模式被改过。可用 dsh_list_modes 查看,dsh_set_mode 在首回合前切换。
- 模式在首回合后锁定:DSH 的 agentPresets.select 只允许空白会话;首回合开始后调用 dsh_set_mode 返回 mode_locked。需要换模式请新建会话。
- 默认完全权限:MCP 新建会话默认 danger-full-access。这是显式的桥接默认,安全边界依赖 loopback/token;不要暴露到不可信网络。
- 需要 DSH Host 正在运行:端点随 DSH Web/Desktop 生命周期存在;DSH 退出后端点消失。
- 审批与用户提问:普通用户提问可通过 dsh_get_questions / dsh_answer_question 查询和回答;权限审批和计划审核不在此能力范围。如果会话触发需要审批的工具,仍须通过 DSH 原来的审批渠道处理,不能通过提问答案绕过。
- 模型选择影响全局默认:见 dsh_set_model 的说明。
- 权限切换只影响当前会话:dsh_set_permission 不改变 DSH GUI 的全局默认权限预设;它只写当前 session 的 permission/preset、sandbox/mode、approval/policy。
- auto / custom 不可由 MCP 选择:auto 是 DSH GUI 的可选集成,custom 是派生状态;MCP 只接受配置中的持久预设(default_options)。
- 客户端收尾依赖 Mcp-Session-Id:不发送这个头的客户端仍可用,但不会被归属、不会自动收尾,dsh_list_client_sessions/dsh_stop_client_sessions 会报 client_identity_required。
- writable_roots 是共同父目录,不是精确白名单:DSH 只支持一个 workspaceRoot。要严格到“只允许这两个目录”,目前只能把它们放进同一个专用父目录,或使用 danger-full-access 并自行约束。
- dsh_list_models.provider_health 只代表本 bridge 进程加载后观察到的一小时:它不是 DSH 全局历史,也不包含启动前的失败。
- 归档会话只读:dsh_get_messages/dsh_get_turn_result/dsh_get_status 可读,dsh_send_message 会返回 session_archived;要在 DSH GUI 里 unarchive 后才能继续。
- 按消息 effort 会短暂改默认模型:底层 selectModel 会把选择保存为“新会话默认”,回合结束后恢复;如果同一时间有别的会话新建,可能看到这个短暂默认值。
- 不要用 status: "idle" 判断成功:超时、408 断流、取消后 Agent 都会回到 idle。用 dsh_get_status.last_turn 或 dsh_get_turn_result 的 finish_reason/ok/error.http_status 判断。
- dsh_send_message 仍是同步的:默认在 HTTP 请求内等待;客户端自身可能有更短的超时。长任务用 wait: false + dsh_wait,timeout_seconds 最大 600,客户端 HTTP 超时要大于它。超时后用 dsh_wait { since } 或 dsh_get_turn_result 收尾,不要自己 sleep 轮询。
- 回合错误默认走两条路:dsh_send_message 把它作为 MCP isError 返回;dsh_wait / dsh_get_turn_result 把它作为正常数据返回(finish_reason: "error"、error.http_status: 408)。
- endpoint 文件可能过期:进程崩溃时不会删除;先用 pid 或 /mcp/info 验证,重启 DSH 后文件会更新。
- 会话标题:本插件不主动设置标题;DSH 会根据首条 prompt 自动生成。
- 单用户:没有多租户或按调用方隔离;所有 MCP 客户端都以 DSH owner 身份操作。
- 归档不是删除:dsh_archive_session 保留日志,只从活动列表隐藏。
9. 开发与测试 bash
npm test
node scripts/smoke.mjs
- test/protocol.test.js:JSON-RPC / MCP 协议、工具分发、错误、批处理、模型选择解析、配置校验。
- test/bridge.test.js:用 fake Host context 覆盖创建、状态/活动/队列、回合结果、长轮询、发送/超时/408、模型别名/能力/健康度、客户端归属/收尾、归档读取、工作区清理、按消息 effort。
- test/turns.test.js:回合边界、usage、工具调用、408/503 状态解析、打开回合的当前工具、指定回合、prompt→turn 映射。
- test/models.test.js:稳定别名、版本排序、别名候选、model_not_found 近似建议。
- test/endpoint.test.js:固定端点文件、原子写入、权限和所有权清理。
- test/http.test.js:真实 node:http 下的初始化、工具调用、状态码、Origin、Bearer token。
- test/questions.test.js:前台/补答生命周期、GUI/MCP 竞态与取消、长轮询通知、重复答案、输入校验、超时原因保留、跳过/自由文本/多选。
- scripts/smoke.mjs:对已安装的真实 DSH 端点做端到端 smoke test(会创建一个真实会话并发送 prompt,可加 --mode、--permission 与 --archive)。默认从 endpoint 文件发现地址。
目录 text index.js Host plugin 入口、路由注册、配置校验、endpoint 文件发布 lib/server.js MCP Streamable HTTP + JSON-RPC 协议层 lib/bridge.js DSH 会话工具实现 lib/turns.js 回合/活动重建、finish_reason 与 HTTP 状态解析 lib/models.js 稳定模型别名、版本排序、近似候选 lib/questions.js 用户提问适配 lib/endpoint.js 固定端点文件(原子写、所有权清理) lib/util.js 参数校验、错误、文本/usage 辅助 test/ node:test 测试 scripts/smoke.mjs 真实端点 smoke test docs/hot-reload.md HMR 热重载调研(触发方式、机制、实测、限制) cordis.patch.yml bundle patch
locale/{en,zh}.json 插件显示名称/描述
icon.svg 插件图标
重新加载
修改源码后无需重启:见 不重启 DSH 热重载插件(HMR)。没有 HMR 时仍需完全重启 DSH Desktop。预览分享卡片长什么样 ↓
分享 · DeepSeek Harness Hub
ahhTou/dsh-mcp-bridge
让外部智能体通过 MCP 调用 DSH 会话与工具
相关插件 · 工具 / 效率
破甲管控台kingcharleslzy-ai/dsh-purge
dsh-purge Community · DeepSeek Harness 社区维护版:宿主兼容、状态诊断、提示词管理、更新与本机备份回滚。基于小杨 / YuJunZhiXue 的 dsh-purge。
★ 4,330已汉化DeepSeek 客户端
74.5
网文长卷lingfengQAQ/webnovel-writer
基于 Claude Code 的长篇网文辅助创作系统,解决 AI 写作中的「遗忘」和「幻觉」问题,支持 200…
★ 7,826已汉化DSH
72.2
花束设计alchaincyf/huashu-design
一句话生成网页、原型、PPT 与信息图等可交付设计
★ 28,638已汉化DSH
71.7
多智能体协作HKUDS/AutoSwarm
AutoSwarm: Unify Your Agents. Unify Your Context (Codex · Claude Code · DeepSeek Harness · Muse · Grok · WorkBuddy· nanobot · and more)
★ 2,349已汉化DSH
71.6
Vox 拼贴视频生成Alisa0808/vox-director
🎬 Vox Director拼贴动效导演
★ 2,129已汉化DSH
71.4
Agent 演化引擎Q00/ouroboros
智能体 OS:智能体自己变聪明,我们只守住底线:面试准入、分阶段评估、带预算的进化循环。MCP 服务器,14 种运行时:Claude Code、Codex CLI、Gemini CLI、OpenCode、Copilot、Kiro 等。
★ 6,311已汉化DSH
71.4
这个插件是你维护的吗?
作者本人可认领并核对本条目;认领通过后,详情页会显示「作者已认领」标记。