🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

AI 客户端桥接 ganmayou2333/dsh-ai-bridge

DeepSeek Harness
ganmayou233329 天前有提交已提供中文文档扫描:含敏感能力

让外部 AI 客户端与豆包互发消息和任务

30/100
综合分
0
GitHub 星标
—
npm 周下载
—/5
用户评分

数据截至 2026/10/3 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add ganmayou2333/dsh-ai-bridge
该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:待复验
是什么
dsh 原生插件 · model
装得上吗
曾在 dsh 0.2.0-rc.2 下真实安装成功,但当前 dsh 已更新至 0.2.1-rc.5,本站尚未在新版下复验
L4 · 真实安装等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 29 天前
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

可执行外部命令读写/删除本地文件读取剪贴板或密钥环境变量发起外部网络请求
查看扫描证据(共 176 处,展示前 118 条)
  • 读取本地文件
    doubao-cdp/cdp.mjs:227
    const cached = JSON.parse(await readFile(file, 'utf8'))
  • 写入本地文件
    doubao-cdp/cdp.mjs:237
    await writeFile(file, `${JSON.stringify({ mode: mode.id, raw: mode.raw, at: Date.now() })}\n`, 'utf8').catch(() => {})
  • 发起网络请求
    doubao-cdp/cdp.mjs:243
    const response = await fetch(`http://127.0.0.1:${PORT}/json/list`)
  • 执行外部命令来自配置/变量
    doubao-cdp/dispatch-selftest.mjs:50
    const child = spawn(process.execPath, [STATUS, ...args], {
  • 执行外部命令来自配置/变量
    doubao-cdp/dispatch-selftest.mjs:66
    const child = spawn(process.execPath, [DISPATCH, ...args], {
  • 读取本地文件
    doubao-cdp/dispatch-selftest.mjs:264
    const dispatchSource = await readFile(DISPATCH, 'utf8')
  • 写入本地文件
    doubao-cdp/dispatch-selftest.mjs:281
    await mkdir(join(scratch, 'with-client'), { recursive: true })
  • 写入本地文件
    doubao-cdp/dispatch-selftest.mjs:282
    await writeFile(
  • 写入本地文件
    doubao-cdp/dispatch-selftest.mjs:338
    mkdirSync(lateDir, { recursive: true })
  • 写入本地文件
    doubao-cdp/dispatch-selftest.mjs:344
    appendFileSync(join(lateDir, 'status.jsonl'), `${JSON.stringify({ v: 1, type: 'status', job: 'LATE-1', state: 'started', at: Date.now() })}\n`)
  • 删除本地文件递归
    doubao-cdp/dispatch-selftest.mjs:360
    rmSync(scratch, { recursive: true, force: true })
  • 读取本地文件
    doubao-cdp/dispatch.mjs:49
    const parsed = JSON.parse(await readFile(join(resolveQueueDir(), 'clients.json'), 'utf8'))
  • 执行外部命令来自配置/变量
    doubao-cdp/dispatch.mjs:72
    const child = spawn(process.execPath, [CDP, ...args], { cwd: HERE, stdio: ['ignore', 'pipe', 'pipe'] })
  • 执行外部命令来自配置/变量
    doubao-cdp/dispatch.mjs:118
    const child = spawn(process.execPath, [join(HERE, 'status.mjs'), 'last', job], {
  • 发起网络请求
    doubao-cdp/doubao.mjs:181
    const response = await fetch(`http://127.0.0.1:${port}/json/version`, {
  • 执行外部命令固定命令
    doubao-cdp/doubao.mjs:207
    const result = spawnSync('tasklist', ['/FI', 'IMAGENAME eq Doubao.exe', '/FO', 'CSV', '/NH'], {
  • 执行外部命令固定命令
    doubao-cdp/doubao.mjs:225
    const result = spawnSync(
  • 读取本地文件
    doubao-cdp/doubao.mjs:245
    const existing = candidatePaths().find((candidate) => existsSync(candidate))
  • 发起网络请求
    doubao-cdp/doubao.mjs:257
    const response = await fetch(`http://127.0.0.1:${port}/json/list`, { signal: AbortSignal.timeout(3000) })
  • 写入本地文件
    doubao-cdp/doubao.mjs:379
    await mkdir(dir, { recursive: true })
  • 写入本地文件
    doubao-cdp/doubao.mjs:380
    await writeFile(probe, '', 'utf8')
  • 删除本地文件
    doubao-cdp/doubao.mjs:381
    await rm(probe, { force: true })
  • 发起网络请求
    doubao-cdp/doubao.mjs:405
    const response = await fetch(`http://127.0.0.1:${port}/json/list`, { signal: AbortSignal.timeout(3000) })
  • 执行外部命令来自配置/变量
    doubao-cdp/preflight-selftest.mjs:34
    const child = spawn(process.execPath, [script, ...args], {
  • 读取本地文件
    doubao-cdp/preflight-selftest.mjs:308
    existsSync(overrideFile) && readFileSync(overrideFile, 'utf8').includes('"type":"override"'),
  • 读取本地文件
    doubao-cdp/preflight-selftest.mjs:308
    existsSync(overrideFile) && readFileSync(overrideFile, 'utf8').includes('"type":"override"'),
  • 写入本地文件
    doubao-cdp/preflight-selftest.mjs:314
    mkdirSync(busyQueue, { recursive: true })
  • 写入本地文件
    doubao-cdp/preflight-selftest.mjs:315
    writeFileSync(
  • 读取本地文件
    doubao-cdp/preflight-selftest.mjs:339
    !existsSync(queueFile),
  • 删除本地文件递归
    doubao-cdp/preflight-selftest.mjs:355
    rmSync(scratch, { recursive: true, force: true })
  • 执行外部命令来自配置/变量
    doubao-cdp/status-push-selftest.mjs:31
    const child = spawn(process.execPath, [script, ...args], {
  • 读取本地文件
    doubao-cdp/status-push-selftest.mjs:52
    return (await readFile(file, 'utf8')).split('\n---\n').filter((entry) => entry.trim().length > 0)
  • 写入本地文件
    doubao-cdp/status-push-selftest.mjs:85
    mkdirSync(fakeDir, { recursive: true })
  • 写入本地文件
    doubao-cdp/status-push-selftest.mjs:87
    writeFileSync(
  • 写入本地文件
    doubao-cdp/status-push-selftest.mjs:103
    writeFileSync(join(fakeDir, 'fake-dsh.cmd'), '@echo off\r\nnode "%~dp0fake-dsh.mjs"\r\n', 'utf8')
  • 写入本地文件
    doubao-cdp/status-push-selftest.mjs:106
    mkdirSync(work, { recursive: true })
  • 写入本地文件
    doubao-cdp/status-push-selftest.mjs:109
    writeFileSync(
  • 写入本地文件
    doubao-cdp/status-push-selftest.mjs:144
    mkdirSync(createdWork, { recursive: true })
  • 读取本地文件
    doubao-cdp/status-push-selftest.mjs:164
    const cursor = JSON.parse(readFileSync(join(createdWork, '.push-cursor.json'), 'utf8'))
  • 读取本地文件
    doubao-cdp/status-push-selftest.mjs:175
    check('假 CLI 的日志文件确实存在(证明走的是真实推送路径)', existsSync(fakeLog))
  • 删除本地文件递归
    doubao-cdp/status-push-selftest.mjs:205
    rmSync(scratch, { recursive: true, force: true })
  • 读取本地文件
    doubao-cdp/status-push.mjs:56
    const parsed = JSON.parse(await readFile(cursorPath(env), 'utf8'))
  • 写入本地文件
    doubao-cdp/status-push.mjs:65
    await mkdir(dirname(file), { recursive: true })
  • 写入本地文件
    doubao-cdp/status-push.mjs:66
    await writeFile(file, `${JSON.stringify(cursor, null, 2)}\n`, 'utf8')
  • 执行外部命令来自配置/变量
    doubao-cdp/status-push.mjs:95
    const child = spawn(useShell ? line : dsh, useShell ? [] : args, {
  • 读取本地文件
    doubao-cdp/status-push.mjs:212
    if (!existsSync(file)) {
  • 执行外部命令来自配置/变量
    doubao-cdp/status-selftest.mjs:30
    const child = spawn(process.execPath, [STATUS, ...args], {
  • 读取本地文件
    doubao-cdp/status-selftest.mjs:51
    return (await readFile(file, 'utf8'))
  • 写入本地文件
    doubao-cdp/status-selftest.mjs:200
    mkdirSync(controlled, { recursive: true })
  • 写入本地文件
    doubao-cdp/status-selftest.mjs:202
    writeFileSync(
  • 写入本地文件
    doubao-cdp/status-selftest.mjs:238
    await appendFile(tornFile, '{"v":1,"type":"status","job":"TORN","sta', 'utf8')
  • 删除本地文件递归
    doubao-cdp/status-selftest.mjs:249
    rmSync(scratch, { recursive: true, force: true })
  • 读取本地文件
    doubao-cdp/status.mjs:113
    if (existsSync(iniPath)) {
  • 读取本地文件
    doubao-cdp/status.mjs:115
    const parsed = parseIni(readFileSync(iniPath, 'utf8'))
  • 读取本地文件
    doubao-cdp/status.mjs:152
    text = await readFile(file, 'utf8')
  • 发起网络请求
    doubao-status-panel/client.js:532
    request = fetch(url, { headers: { accept: 'application/json' }, cache: 'no-store' })
  • 发起网络请求
    doubao-status-panel/client.js:561
    request = fetch(MODE_PATH, {
  • 发起网络请求
    doubao-status-panel/client.js:865
    fetch('/doubao-status/api?sample=0')
  • 读取本地文件
    doubao-status-panel/index.js:702
    parsed = JSON.parse(await readFile(file, 'utf8'))
  • 读取本地文件
    doubao-status-panel/panel-selftest.mjs:87
    const source = readFileSync(join(HERE, 'client.js'), 'utf8')
  • 写入本地文件
    doubao-status-panel/panel-selftest.mjs:226
    mkdirSync(dirname(file), { recursive: true })
  • 写入本地文件
    doubao-status-panel/panel-selftest.mjs:227
    appendFileSync(file, events.map((event) => `${JSON.stringify(event)}\n`).join(''), 'utf8')
  • 读取本地文件
    doubao-status-panel/panel-selftest.mjs:241
    const manifest = JSON.parse(readFileSync(join(HERE, 'package.json'), 'utf8'))
  • 读取本地文件
    doubao-status-panel/panel-selftest.mjs:260
    const patch = readFileSync(join(HERE, 'cordis.patch.yml'), 'utf8')
  • 读取本地文件
    doubao-status-panel/panel-selftest.mjs:268
    const hostSource = readFileSync(join(HERE, 'index.js'), 'utf8')
  • 读取本地文件
    doubao-status-panel/panel-selftest.mjs:271
    const clientSource = readFileSync(join(HERE, 'client.js'), 'utf8')
  • 写入本地文件
    doubao-status-panel/panel-selftest.mjs:798
    mkdirSync(join(connectorHome, 'mcp-connector'), { recursive: true })
  • 写入本地文件
    doubao-status-panel/panel-selftest.mjs:800
    appendFileSync(
  • 写入本地文件
    doubao-status-panel/panel-selftest.mjs:832
    mkdirSync(join(brokenHome, 'mcp-connector'), { recursive: true })
  • 写入本地文件
    doubao-status-panel/panel-selftest.mjs:833
    appendFileSync(join(brokenHome, 'mcp-connector', 'clients.json'), '{ not json', 'utf8')
  • 删除本地文件递归
    doubao-status-panel/panel-selftest.mjs:857
    rmSync(connectorHome, { recursive: true, force: true })
  • 删除本地文件递归
    doubao-status-panel/panel-selftest.mjs:858
    rmSync(emptyHome, { recursive: true, force: true })
  • 删除本地文件递归
    doubao-status-panel/panel-selftest.mjs:859
    rmSync(brokenHome, { recursive: true, force: true })
  • 删除本地文件递归
    doubao-status-panel/panel-selftest.mjs:1246
    rmSync(scratch, { recursive: true, force: true })
  • 写入本地文件
    dsh-mcp-connector/queue.mjs:55
    await mkdir(dirname(target), { recursive: true })
  • 写入本地文件
    dsh-mcp-connector/queue.mjs:60
    await mkdir(lockDir)
  • 读取本地文件
    dsh-mcp-connector/queue.mjs:65
    const info = await stat(lockDir)
  • 删除本地文件递归
    dsh-mcp-connector/queue.mjs:67
    await rm(lockDir, { recursive: true, force: true })
  • 删除本地文件递归
    dsh-mcp-connector/queue.mjs:82
    await rm(lockDir, { recursive: true, force: true }).catch(() => {})
  • 读取本地文件
    dsh-mcp-connector/queue.mjs:90
    raw = await readFile(file, 'utf8')
  • 写入本地文件
    dsh-mcp-connector/queue.mjs:184
    await mkdir(dirname(file), { recursive: true })
  • 写入本地文件
    dsh-mcp-connector/queue.mjs:186
    await appendFile(file, `${prefix}${JSON.stringify(record)}\n`, 'utf8')
  • 写入本地文件
    dsh-mcp-connector/selftest.mjs:63
    mkdirSync(SCRATCH_HOME, { recursive: true })
  • 删除本地文件递归
    dsh-mcp-connector/selftest.mjs:76
    rmSync(SCRATCH_HOME, { recursive: true, force: true })
  • 执行外部命令来自配置/变量
    dsh-mcp-connector/selftest.mjs:83
    const child = spawn(process.execPath, [SERVER_PATH], {
  • 发起网络请求
    dsh-mcp-connector/selftest.mjs:145
    const init = await server.request('initialize', {
  • 发起网络请求
    dsh-mcp-connector/selftest.mjs:156
    const list = await server.request('tools/list', {})
  • 发起网络请求
    dsh-mcp-connector/selftest.mjs:165
    const info2 = await server.request('tools/call', { name: 'dsh_cli_info', arguments: {} }, 90_000)
  • 发起网络请求
    dsh-mcp-connector/selftest.mjs:170
    const bad = await server.request('tools/call', { name: 'nope', arguments: {} })
  • 发起网络请求
    dsh-mcp-connector/selftest.mjs:174
    const status = await server.request('tools/call', { name: 'connector_status', arguments: {} })
  • 写入本地文件
    dsh-mcp-connector/selftest.mjs:188
    await writeFile(
  • 发起网络请求
    dsh-mcp-connector/selftest.mjs:206
    await statusServer.request('initialize', { protocolVersion: '2025-11-25', clientInfo: { name: 'selftest-status', version: '1' } })
  • 读取本地文件
    dsh-mcp-connector/selftest.mjs:216
    const recorded = JSON.parse(await readFile(statusArgvFile, 'utf8'))
  • 删除本地文件
    dsh-mcp-connector/selftest.mjs:249
    rmSync(fakeStatus, { force: true })
  • 删除本地文件
    dsh-mcp-connector/selftest.mjs:250
    rmSync(statusArgvFile, { force: true })
  • 写入本地文件
    dsh-mcp-connector/selftest.mjs:281
    mkdirSync(join(linkDir, 'real'), { recursive: true })
  • 写入本地文件
    dsh-mcp-connector/selftest.mjs:284
    await writeFile(
  • 读取本地文件
    dsh-mcp-connector/selftest.mjs:304
    if (existsSync(junctionPath)) {
  • 读取本地文件
    dsh-mcp-connector/selftest.mjs:322
    linked.result?.isError === false && existsSync(guardedMark),
  • 删除本地文件递归
    dsh-mcp-connector/selftest.mjs:329
    rmSync(linkDir, { recursive: true, force: true })
  • 写入本地文件
    dsh-mcp-connector/selftest.mjs:341
    mkdirSync(flowDir, { recursive: true })
  • 写入本地文件
    dsh-mcp-connector/selftest.mjs:342
    await writeFile(
  • 读取本地文件
    dsh-mcp-connector/selftest.mjs:367
    const events = (await readFile(flowQueue, 'utf8'))
  • 读取本地文件
    dsh-mcp-connector/selftest.mjs:391
    const reportedArgv = (await readFile(flowArgvFile, 'utf8'))
  • 读取本地文件
    dsh-mcp-connector/selftest.mjs:441
    const foldedDirect = (await readFile(flowQueue, 'utf8'))
  • 删除本地文件递归
    dsh-mcp-connector/selftest.mjs:454
    rmSync(flowDir, { recursive: true, force: true })
  • 执行外部命令来自配置/变量
    dsh-mcp-connector/selftest.mjs:513
    const child = spawn(process.execPath, [SERVER_PATH, '--http', '--port', String(port), '--token', token], {
  • 删除本地文件递归
    dsh-mcp-connector/selftest.mjs:627
    rmSync(join(HERE, '.selftest-queue'), { recursive: true, force: true })
  • 执行外部命令来自配置/变量
    dsh-mcp-connector/selftest.mjs:699
    const child = spawn(process.execPath, [join(HERE, 'queue.mjs'), ...argv], {
  • 执行外部命令来自配置/变量
    dsh-mcp-connector/selftest.mjs:773
    const child = spawn(
  • 执行外部命令来自配置/变量
    dsh-mcp-connector/server.mjs:126
    child = spawn(plan.file, plan.argv, {
  • 读取本地文件
    dsh-mcp-connector/server.mjs:424
    const parsed = JSON.parse(await readFile(file, 'utf8'))
  • 写入本地文件
    dsh-mcp-connector/server.mjs:450
    await mkdir(dir, { recursive: true })
  • 写入本地文件
    dsh-mcp-connector/server.mjs:451
    await writeFile(file, `${JSON.stringify(state, null, 2)}\n`, 'utf8')
  • 读取本地文件
    dsh-mcp-connector/server.mjs:461
    const parsed = JSON.parse(await readFile(join(resolveQueueDir(), CLIENT_STATE_FILE), 'utf8'))
  • 读取本地文件
    dsh-mcp-connector/server.mjs:516
    if (!existsSync(candidate)) continue
  • 执行外部命令来自配置/变量
    dsh-mcp-connector/server.mjs:540
    child = spawn(process.execPath, [script, ...args], {
  • 读取密钥环境变量
    dsh-mcp-connector/server.mjs:1065
    token: process.env.DSH_MCP_TOKEN?.trim() || undefined,
扫描于 2026/10/31(0 天前) · 范围 全包扫描 · 来源 GitHub 仓库

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

功能亮点

  • 接入本地DSH
  • 消息送入DSH
  • 派发任务给豆包

把外部 AI 客户端接到本地 DeepSeek Harness(DSH):零依赖 MCP 服务器,把消息送进 DSH;再加 CDP 驱动,把任务派发给豆包桌面应用。

README

由 DeepSeek 最新模型翻译生成
dsh-ai-bridge 把外部 AI 客户端和本机 DeepSeek Harness(DSH)接起来的两件小工具,零依赖,不动 DSH 本体。 两件零依赖工具,把外部 AI 客户端和本机 DeepSeek Harness(DSH)接起来:一个 MCP server,让任何支持 MCP 的客户端把消息发进 DSH;一个 CDP 驱动,把任务下发进豆包桌面版。 外部 AI 客户端 ──MCP──▶ dsh-mcp-connector ──▶ dsh --profile headless ──▶ DSH (Claude Code / Cursor / VS Code / Codex / Kimi Code / CodeBuddy / Qoder / TRAE / ZCode / Step Code / MiniMax Code / Coze …
展开完整文档(README 剩余 1 节)
dsh-ai-bridge

把外部 AI 客户端和本机 DeepSeek Harness(DSH)接起来的两件小工具,零依赖,不动 DSH 本体。

两件零依赖工具,把外部 AI 客户端和本机 DeepSeek Harness(DSH)接起来:一个 MCP server,让任何支持 MCP 的客户端把消息发进 DSH;一个 CDP 驱动,把任务下发进豆包桌面版。

外部 AI 客户端 ──MCP──▶ dsh-mcp-connector ──▶ dsh --profile headless ──▶ DSH (Claude Code / Cursor / VS Code / Codex / Kimi Code / CodeBuddy / Qoder / TRAE / ZCode / Step Code / MiniMax Code / Coze …)

DSH / 脚本 / HTTP ──入队──▶ tasks.jsonl ──task_claim──▶ 任意 MCP 客户端(拉取式任务队列)

脚本 / DSH ──CDP──▶ doubao-cdp ──▶ 豆包桌面版(把任务下发进它的聊天框)

为什么需要它

DSH 随发行体只带了 MCP 客户端(dsh-mcp-client),没有 MCP server 半区;它的 Web API 又需要浏览器 cookie 与 Host/Origin 围栏,第三方程序难以直接持有。所以「让别的 AI 给 DSH 发消息」必须有一个独立桥——这就是本仓库的第一个工具。

反方向(把任务下发给别家 AI)没有任何官方入站 API,只能走 GUI 自动化——这是第二个工具。

而 MCP 工具只在客户端自己的回合里执行,推送不进去;所以真正能自动化的派活方式是拉取:生产者入队,客户端领取。这部分已经做进第一个工具(task_claim / task_complete / task_publish / task_list + POST /tasks)。

目录

| 路径 | 作用 |
| dsh-mcp-connector/ | MCP server(stdio + Streamable HTTP 双传输),暴露 dsh_ask / dsh_cli_info / connector_status 与四个任务队列工具,驱动 dsh --profile headless |
| doubao-cdp/ | 用 Chrome DevTools Protocol 驱动豆包桌面版(Electron):cdp.mjs 下发并读回,dispatch.mjs 带副作用校验的派发(拒绝相信客户端的自述) |
| skills/dsh-mcp-connector/ | 给 AI agent 用的技能包:17 个 MCP 客户端的配置矩阵、陷阱表、以及「未证实项」清单 |
| docs/design-notes.md | 设计说明:为什么必须有独立的桥、DSH 的五个机器入口面、队列为何是拉取式、两个方向的合规结论、已知失败模式 |
| scripts/register.mjs | 注册助手:把连接器写进各 MCP 客户端的配置文件,不用手抄 JSON/TOML |

各自的使用方法见对应 README。

快速开始

git clone https://github.com/ganmayou2333/dsh-ai-bridge
cd dsh-ai-bridge

npm test # 两套密封测试:不需要 DSH,也不需要豆包

cd dsh-mcp-connector
node selftest.mjs # 完整自测(多跑两项需要本机 DSH 的检查)

然后在你的 MCP 客户端里加一条 stdio server:命令: node 参数: <你的路径>/dsh-ai-bridge/dsh-mcp-connector/server.mjs 环境: DSH_BIN=<dsh 可执行文件> DSH_WORKSPACE=<DSH 会话工作目录> 逐客户端配置见 dsh-mcp-connector/README.md

用注册助手代替手抄配置

node scripts/register.mjs --list # 看每个客户端的配置文件位置与注册状态
node scripts/register.mjs --client cursor --dry-run # 只打印将要写入的内容,不落盘
node scripts/register.mjs --client vscode # 写入(已存在的文件会先备份成 .bak)
node scripts/register.mjs --client all --dry-run # 一次看全部

支持的客户端与各自的坑都编码在里面(ZCode 的 mcp.servers、Step Code 的 TOML、CodeBuddy/MiniMax 必须显式 type: stdio、VS Code 的 servers)。claude-code 是 CLI 管理的,脚本不写文件,只打印该运行的 claude mcp add 命令。

写入前建议先 --dry-run 看一眼;测试用 --root/--project 重定向到临时目录,不会碰你的真实配置。

要求:Node ≥ 20(本机用 v24 验证)、一个可用的 DSH 安装(dsh 命令)。

验证状态

| 项 | 证据 |
| dsh-mcp-connector 自测 | 61 项断言全过,退出码 0(密封 DSH_HOME 下 headless profile 首次初始化、stdio 往返、HTTP 鉴权 401/403/404/405/202、生产者路由 201、队列语义与 6 路并发领取无碰撞、自观测与流水线握手闸门) |
| 离线自测(CI 用) | 58 项通过 / 2 项跳过,退出码 0;跳过的两项需要本机装好 DSH。工作流见 .github/workflows/ci.yml,矩阵为 Ubuntu/Windows × Node 20/24 |
| 真实模型调用 | 一条消息 3.7 秒返回,拿到 sessionId;带该 id 再发一条成功续接同一会话 |
| 真实 MCP 客户端握手 | Claude Code 2.1.226 完成握手并协商到协议 2025-11-25,证据是连接器自己记录的 clients.json(该客户端当时未登录,故工具调用未执行) |
| doubao-cdp 真实往返 | 连续 3 次下发-回传成功,最后一次为 send + wait 单次调用完成 |
| dispatch.mjs 校验逻辑 | 成功路径(队列完成 → 退出 0 VERIFIED)与失败路径(客户端嘴上说做了、队列仍 pending → 退出 2 UNVERIFIED)双向实测;密封测试 15 项断言全过(含发送前预检),不需要豆包与 CDP |
| 豆包启动确认 | 14 项断言全过:端口关闭 / 端口通但无匹配页面 / 就绪 / --no-preflight 逃生门 四种状态,外加「确认失败不得留下孤儿任务」。用假 CDP 端点密封测,不需要豆包 |
| 状态回报 | 28 项断言全过:固定词表与别名、ini 开关、被关掉的状态静默 no-op、限流(关键状态永不丢)、截断、终态不可覆盖、崩溃半行、时间线排序、忙闲与疑似卡死 |
| 调用前五维识别 | 23 项断言全过:连通 / 模式 / 命令能力 / 忙闲 / 通道,三态区分(0 可发 / 3 未就绪 / 8 无法识别),模式不匹配与忙闲都拒绝,--force 放行并留 override 记录 |
| 状态推送(P2) | 16 项断言全过:只推阶段转换(progress 不推)、游标幂等、新增事件会推、缺会话时明确报错、--create-session 只建一次会话 |
| 统一入口 | 仓库根 npm test = 连接器离线自测 + 派发器 + 豆包确认 + 状态 + 状态推送 + 注册助手,共六套,退出码 0 |

自测默认不产生模型费用;DSH_MCP_CONNECTOR_LIVE=1 node selftest.mjs 会追加一次真实调用。

安全须知

- dsh-mcp-connector 的 stdio 模式由客户端进程本身充当信任边界;HTTP 模式强制 Bearer token,默认只绑 127.0.0.1,未列入白名单的 Origin 一律 403。要上公网请再套 TLS 反向代理并换掉示例 token。
- 外部投递的消息在 DSH 内以 {kind:"user"} 进入会话,与真人输入不可区分——请让下游在任务文本里自带来源标记。
- 不要相信任何 AI 客户端的"我已经调用了工具"。实测:豆包在连接器未启用时会回复「已调用 task_claim,status 为 ok」,而队列里那条任务仍是 pending。核实副作用(队列事件、文件、日志)才是唯一证据——doubao-cdp/dispatch.mjs 就是按这个原则写的。
- doubao-cdp 需要给豆包加 --remote-debugging-port,这意味着本机任何进程都能接管你的豆包(读聊天、以你身份发消息)。不用时退出并正常启动。
- 两者都只做消息投递:不要把外部订阅凭证当作 DSH 的模型后端,那违反各家消费者条款。

许可

MIT,见 LICENSE。
预览分享卡片长什么样 ↓ganmayou2333/dsh-ai-bridge
分享 · DeepSeek Harness Hub
ganmayou2333/dsh-ai-bridge

让外部 AI 客户端与豆包互发消息和任务

回到顶部