安装
在 DeepSeek Harness 里通过 dsh-market 安装
dsh plugin --profile web add dshmarket
或使用命令行
dsh plugin --profile web add github:toddpan/dsh-xiaozhi
装任何插件都等于在你的机器上跑第三方代码,权限和你本人一样大——能读你的文件、用你的凭据、访问网络。请先审阅源码,并尽量锁定 commit(github:owner/repo#sha)。
截图
README
把小智(Xiaozhi)语音助手接入 DeepSeek Harness(DSH)Web:DSH 作为 MCP 工具提供方, 通过 WebSocket 上的 JSON-RPC 2.0 把工作区、会话、模型、设置、文件等能力封装成工具,供小智语音调用。
把小智(Xiaozhi)语音助手接入 DSH Web:DSH 作为 MCP 工具提供方,把 35 个 DSH Web 接口封装成 16 个 语音友好工具,自带 DSH Web 设置页。 Connect the Xiaozhi voice assistant to DSH Web as an MCP tool provider — 35 endpoints, 16 voice-friendly tools.
实施前的设计提案(架构 ADR、v2 复核、设置页 UX 走查)归档在 docs/design/,其中与实际交付不一致的地方已逐条注明。
1. 它解决什么问题
DSH Web 的能力都在 HTTP REST 接口上,而小智只认 MCP(Model Context Protocol)。 本插件坐在两者中间:
你说一句话
│
▼
┌─────────────┐ MCP(JSON-RPC 2.0 / WebSocket) ┌──────────────────────────┐
│ 小智助手 │ ◄───────────────────────────────► │ dsh-xiaozhi (Host 半边) │
│ (App/硬件) │ initialize / tools/list / call │ ├ MCP 会话与工具注册表 │
└─────────────┘ │ ├ 能力→REST 路由映射 │
│ └ LocalInvoker(进程内调用)│
└───────────┬──────────────┘
│ 不走网络,直接调用
▼
┌──────────────────────────┐
│ DSH Web REST 路由(内置副本)│
└──────────────────────────┘
三个关键设计决定:
- DSH 永远是 MCP 的“服务端/工具提供方”。 两种传输方式下都由 DSH 应答
initialize/ping/tools/list/tools/call,从不主动发起这些请求。 - 默认主动外连(
endpoint模式)。 DSH 作为 WebSocket 客户端连到小智官方 MCP 接入点,所以不需要公网 IP、端口映射或反向代理。 - 进程内调用,而不是回环 HTTP。 工具调用通过
LocalInvoker直接打到内置的 DSH REST 路由,无需猜测 DSH 自身的 host/port/鉴权,也不依赖外部服务。
2. 快速开始(3 步)
前提:DSH Web 已经在跑(dsh web,默认 http://127.0.0.1:3080);你有小智账号并能打开它的
「MCP 接入点」页面。
安装插件(在本仓库目录下执行):
dsh plugin add https://github.com/toddpan/dsh-xiaozhi也可以在 DSH Web 的「设置 → 插件」里用「安装本地目录」选择该目录。
拿到接入点地址并添加设备:打开 DSH Web →「设置 → 小智接入」→「接入配置」, 点「添加设备」,把小智后台的 MCP 接入点 WebSocket 地址(形如
wss://api.xiaozhi.me/mcp/?token=…)粘贴进去,点「保存并重载」。 可以重复添加,同时绑定多台小智设备/智能体,每台独立连接、独立显示状态。看状态:回到「状态」页签,连接状态应为
connected;状态页每 3 秒自动刷新, 点「立即重连」或某台设备的「重连」后,无需手动刷新即可看到状态变化。 点一次「测试连接」可以看到真实握手结果。然后在手机上对小智说:“用 dsh 看一下我的会话列表” 或 “让 DSH 汇报一下运行状态”。
接入点地址含 token,属于敏感信息。设置页读回时只显示
token=***, 保存时也会被识别为「未修改」,不会把掩码写进配置。详见 §7。
3. 两种传输方式
endpoint(默认,推荐) |
server(自建服务端) |
|
|---|---|---|
| 谁发起连接 | DSH 主动外连小智接入点 | 小智服务端连到 DSH |
| 需要公网可达吗 | 不需要 | 需要(或反向代理 / 内网同段) |
| 主要配置 | endpoints(多设备列表)、endpointHeaders |
serverPath、serverPort、serverToken |
| 适用场景 | 小智官方 MCP 接入点、家用/办公本机 | 自建 xiaozhi-esp32-server、内网统一网关 |
两种方式可以同时开启:mode 决定主通道,serverPort > 0 时会额外在 0.0.0.0 上
监听一个独立端口。
多设备绑定:endpoint 模式支持同时绑定多台小智设备(多个智能体的 MCP 接入点)。
每台设备一条独立的 WebSocket 连接,各自带重连退避与心跳;设置页按设备显示状态、
支持单台「重连 / 测试」,删除设备也只影响该设备。设备列表保存在 endpoints
(见 §8);旧版单设备配置(endpointUrl)仍然生效,设置页读回时会显示为一台设备,
首次保存后自动迁移为列表形态。
断线重连:endpoint 模式带指数退避(reconnectMinMs → reconnectMaxMs,含 ±20% 抖动)
和 heartbeatMs 心跳;「状态」页的「重连次数」和日志可以看到全部过程。
4. 工具暴露方式:grouped(默认)还是 flat
小智的工具名会被清洗成 [A-Za-z0-9_\-中文],本插件的所有工具名都是该规则的不动点
(例如 dsh_session_history),因此不会在平台侧被改名。
| 模式 | 工具数 | 说明 |
|---|---|---|
grouped(默认) |
16(关闭工具组后更少) | 按能力域合并,用 action 参数选择具体动作 |
flat |
35 | 每个接口一个工具,名字与接口一一对应 |
默认选 grouped 的原因:语音模型在 35 个工具里挑一个的准确率明显低于在 16 个里挑。 超过 24 个工具时设置页会给出提示。完整对照表见 docs/TOOLS.md。
工具组开关:可在「接入配置 → 工具组开关」里关掉暂时不用的域(例如 docs、files)。
关闭整组会让对应工具(或分组工具里的对应动作)直接不可用。
写入开关:allowWriteTools = false 时,创建/修改/删除/发送类动作会被拒绝并返回一句
可直接朗读的中文说明,只读动作照常可用(即使它们和写动作合并在同一个 grouped 工具里)。
5. 能力覆盖
35 个接口全部可达,两种工具模式下都覆盖 35/35:
| 能力域 | 能力数 | 对应接口 |
|---|---|---|
| 系统状态 | 1 | GET /system/status |
| 工作区 | 6 | /workspaces、/workspaces/:id、/workspaces/:id/sessions |
| 会话 | 13 | /sessions、/sessions/:id、history、stats、todos、skills、questions、answers、cancel、events |
| 文件 | 3 | /sessions/:id/files、/files/download |
| 对话 | 3 | /sessions/:id/prompt、/prompt-stream、/chat/completions |
| 模型与预设 | 5 | /models、/models/default、/providers、/presets |
| 系统设置 | 2 | /settings、/settings/:namespace |
| 接口文档 | 2 | /docs、/openapi.json |
其中 4 个能力在 MCP 语义下做了降级(不是缺失,但仍需你知情),详见下一节。
6. MCP 语义降级(务必阅读)
MCP 的 tools/call 是一问一答的,没有增量流式通道,而原 REST 接口里有几个是流式的。
本插件选择「尽量保住语义、并如实告知」而不是假装支持:
| 能力 | 原本形态 | 在 MCP 上的行为 | 你需要知道 |
|---|---|---|---|
conversation.promptStream(dsh_say / dsh_conversation_promptstream) |
text/event-stream,增量推送 |
DSH 在服务端收集完整个流后一次性返回结果文本 | 语音端不会逐步流式;promptTimeoutMs 决定等待上限,超时返回「已提交、仍在运行」而不是错误 |
sessions.events(dsh_session_watch) |
常驻 SSE 事件流 | 只在有限时间窗内(1–30 秒)收集事件后返回 | 只能当“看一眼最近的动静”,不能当实时监听;需要持续监听请用 sessions.stats 轮询 |
files.download |
二进制文件流 | 文本文件回传正文(截断到 maxVoiceChars);二进制只回传摘要(大小、类型、路径) |
语音播报二进制内容本来也没有意义;需要真文件请走 DSH Web 界面或内置 REST 层 |
docs.openapi |
完整 OpenAPI JSON | 返回结构摘要(openapi 版本、title、路径数、最多 100 条路径、字节数、原始 URL) |
完整规范请直接访问 apiBase/openapi.json |
另外两点:
dsh_say(wait=false)用于「把话转给会话、不等结果」:它在约 1.5 秒预算内提交prompt-stream,超时就静默返回「已提交」并附上会话当前状态,不会让你干等。- 所有工具结果都会按
maxVoiceChars截断成单个 text 块,避免语音播报冗长。
7. 安全模型(请按自己的部署范围核对)
| 面 | 默认 | 保护 |
|---|---|---|
DSH Web 设置页 API /dsh-xiaozhi/admin |
仅本机可访问(DSH 默认绑定 127.0.0.1) |
①跨站 Origin 拒绝 ②sec-fetch-site: cross-site 拒绝 ③每个请求(含读取)都必须带 x-dsh-xiaozhi-admin: 1 自定义头;跨站表单/图片无法设置自定义头,跨域 fetch 会触发预检而本路由 cors: false 从不放行 ④若宿主存在 connection 服务,先由它做浏览器 cookie + Host/Origin 判定(401/403) |
内置 DSH REST 层 /dsh-xiaozhi/api/v1 |
默认开启 | 设置 apiKey 后需 Authorization: Bearer … 或 X-API-Key;未设置密钥时会给出警告 |
| MCP 工具(对外) | 默认开启,默认允许写 | allowWriteTools=false 关闭全部写操作;disabledGroups 缩小攻击面 |
server 模式独立端口 |
默认关闭(serverPort=0) |
填端口会在 0.0.0.0 监听,必须设置 serverToken,否则设置页会警告 |
密钥掩码:设置页读回配置时,apiKey、serverToken、接入点 URL 里的 token=(含
endpoints 里每台设备的 URL)以及 endpointHeaders / 设备 headers 的所有值
都被替换成掩码(•••••• / ***),但会保留 header 名字。
保存时掩码会被识别为“未修改”并按存储值还原,不会用掩码覆盖真实密钥。
endpointHeaders(全局兜底)只能新增/覆盖,不能通过设置页删除(底层是合并写入);
要删掉某个全局请求头,手工编辑 settings.json。设备级 headers 则以整行替换的方式保存,
在设备卡片里删掉对应行并保存即可删除该键。
8. 配置项
配置分三层,优先级从低到高:
- 代码默认值(
src/config.ts的DEFAULTS) - 插件行的
config(profile 的cordis.patch.yml) - 设置页保存的覆盖(
<homeDir>/settings.json)
| 配置 | 默认 | 说明 |
|---|---|---|
enabled |
true |
关闭后小智无法调用任何工具 |
mode |
endpoint |
endpoint / server |
endpoints |
[] |
小智 MCP 设备列表({id?, name?, url, headers?}),非空时优先生效;设置页「添加设备」写的就是这个键 |
endpointUrl |
'' |
(旧单设备)小智 MCP 接入点;endpoints 非空时被忽略 |
endpointHeaders |
{} |
接入点附加请求头(所有设备的兜底,设备级 headers 覆盖同名键) |
serverPath |
/mcp/xiaozhi |
server 模式的路径(须含 /mcp/) |
serverPort |
0 |
0 复用 DSH Web 服务器;>0 额外监听 0.0.0.0 |
serverToken |
'' |
serverPort>0 时强烈建议设置 |
toolMode |
grouped |
grouped / flat |
disabledGroups |
[] |
关闭的能力域 |
allowWriteTools |
true |
是否允许写操作 |
promptTimeoutMs |
120000 |
语音指令等待上限(须小于内置 REST 层的 180000) |
maxVoiceChars |
700 |
单条回复截断长度 |
listLimit |
10 |
列表类结果条数 |
heartbeatMs |
30000 |
心跳间隔 |
reconnectMinMs / reconnectMaxMs |
1000 / 30000 |
重连退避区间 |
apiPathPrefix |
/dsh-xiaozhi/api |
内置 REST 层前缀(设置页 API 固定在 /dsh-xiaozhi/admin) |
exposeDshApi |
true |
是否挂载内置 DSH REST 层 |
apiKey |
'' |
内置 REST 层鉴权密钥 |
cors |
false |
内置 REST 层是否允许跨域 |
defaultCwd |
'' |
创建会话的默认目录 |
maxUploadBytes |
104857600 |
上传上限 |
homeDir |
'' |
只能在插件行 config 里设置(见下) |
logToolCalls |
true |
记录每次工具调用 |
sendInitializedNotification |
true |
MCP 握手后发送 notifications/initialized |
serverName |
DSH |
对外声明的服务名 |
为什么
homeDir不在设置页里? 它决定设定文件本身放在哪,如果允许从该文件里读, 就会出现“设置页显示新目录、覆盖却仍写旧目录”的自相矛盾。因此homeDir固定只从插件行config读取,设置页只做只读展示。环境变量DSH_XIAOZHI_HOME亦可指定。
9. 设置页
DSH Web →「设置 → 小智接入」,共 5 个页签:
- 状态:连接状态徽标、传输方式、接入点(已掩码)、每台已绑定设备的状态行、 已连接客户端数、重连次数、最近错误、需要注意的警告、对外地址、工具/能力计数、 各工具组开关状态;可「测试连接」「立即重连」「刷新」。状态每 3 秒自动刷新, 重连后无需手动点刷新。
- 接入配置:基础项 + 小智 MCP 设备列表(添加/重命名/删除,每台可单独「重连」「测试」)
- 工具组开关 + 折叠的高级项;「保存并重载」会写覆盖文件并重启运行时, 「恢复默认」清空全部覆盖。
- 工具清单:当前实际暴露的工具、读写属性、覆盖的能力数。
- 能力清单:35 个能力按域列出,含方法与路径。
- 日志:插件环形日志(默认 300 条),可打开 5 秒自动刷新。
页面只用 DSH 主题 token(--dsw-alias-*)着色,不依赖 dsh-client-ui-primitives,
因此明暗主题都跟随宿主,样式不会和宿主冲突。
10. 开发与验证
cd dsh-xiaozhi
bash scripts/build.sh # 需要 DSH 源码 checkout 提供 tsc(自动探测)
node --test --test-timeout=30000 "test/*.test.mjs"
测试覆盖(127 个用例):
| 文件 | 覆盖内容 |
|---|---|
test/protocol.test.mjs |
MCP 报文、工具名清洗不动点、信封解析 |
test/ws.test.mjs |
RFC 6455 分帧、掩码方向、分片、关闭握手 |
test/config.test.mjs |
三层配置合并、密钥掩码、homeDir 不可覆盖 |
test/endpoints.test.mjs |
多设备:endpoints 归一化与旧键兼容、设备掩码还原、每台设备独立拨号与状态 |
test/coverage.test.mjs |
35 个接口逐条钉住;两种工具编织都全覆盖;工具名是清洗不动点 |
test/dispatcher.test.mjs |
进程内调用:JSON、查询串、请求体、流式响应、404、超时 504 |
test/mcp-session.test.mjs |
真实 socket 上的握手 → tools/list → tools/call,含并发与协议错误 |
test/routes.test.mjs |
用真实路由表验证 35 个能力都命中已注册路由;分组工具端到端 |
test/client.test.mjs |
浏览器半边的常量一致性、双语字典完整性、helpers、react-dom/server 渲染 |
test/admin.test.mjs |
设置页 API:页面调用的每个路由都必须以正确方法可达;三层守卫;密钥掩码剥离 |
test/docs.test.mjs |
文档与代码一致性:工具名/能力/计数不得漂移 |
src/dshapi/ 是上游 @dsh-external/dsh-web-service v0.1.11 的逐字拷贝(BSD-3-Clause),
唯一的新文件是把它组装成单个路由的 src/dshapi/service.ts,这样上游更新时仍是干净的三方 diff。
详见 NOTICE。
11. 常见问题
| 现象 | 原因与处理 |
|---|---|
状态一直是 disconnected |
设备的接入点地址没填或填错(必须是 ws:///wss:// 且含 /mcp/,且不能含 key/call 字样);看「日志」页首个错误;多台设备时看「状态」页对应设备行的错误说明 |
| 小智能看到工具但调用失败 | 检查 allowWriteTools;写入类动作被关闭时会返回明确的中文说明 |
| 小智看不到任何工具 | enabled=false,或所有工具组都被关闭 |
| 语音里会话/工作区 id 说不清 | grouped 工具返回的 id 都做了缩短(如 sess-123);也可以用名称调用 |
| 局域网里的自建小智连不上 | server 模式且 serverPort=0 时只监听 DSH 服务器(默认仅本机);填端口并设置 serverToken |
修改 apiPathPrefix 后设置页没变 |
符合预期:设置页 API 固定在 /dsh-xiaozhi/admin,apiPathPrefix 只影响内置 REST 层 |
12. 许可
BSD-3-Clause。派生自 @dsh-external/dsh-web-service v0.1.11(Copyright © 2026 toddpan 潘祖继),
同一许可。见 LICENSE 与 NOTICE。
评论
评论存放在 GitHub Discussions。用 GitHub 账号登录后可发表评论或点表情。