Skip to main content
Glama
wjxn13

dsh-video-evidence

by wjxn13

dsh-video-evidence

兼容的 Harness 版本

  • 已验证支持:DeepSeek Harness 0.1.0-rc.6(通过 @deepseek-ai/dsh-mcp-client,跨 profile 可用)。

  • 最新版 0.1.2-alpha.3:适配计划进行中,暂无确切完成时间。升级前请先备份你的 DSH 安装。

DeepSeek Harness (dsh) 的 MCP 插件:按问题检索 B 站视频,并定位到真正讲到该内容的片段(带时间戳、可直接回跳)。

不是"搜到视频"就结束,而是回答"这个问题的答案在视频的第几分钟"。

用户:Spring Boot 项目怎么创建?

→ 1. https://www.bilibili.com/video/BV17m411d7Ny?t=0
     环境准备、JDK 版本选择 …
→ 2. https://www.bilibili.com/video/BV17m411d7Ny?t=60
     打开 IDEA,新建 Project,选择 Spring Initializr …
→ 3. https://www.bilibili.com/video/BV17m411d7Ny?t=90
     填写 Group / Artifact,勾选依赖,Finish …

Related MCP server: vid-agent-mcp

为什么做这个

市面上的"跨模态检索 / 智能搜索"插件,绝大多数做的是网页和文本这一层:给一段查询,返回一堆链接和摘要。这一层已经很挤了,重复造轮子没有意义。

真正还没有被好好解决的,是视频内部的片段级定位

  • 搜索引擎只能告诉你"这个视频可能相关",不会告诉你相关的是哪 30 秒;

  • 视频平台自带的字幕搜索依赖 UP 主上传字幕,覆盖率和质量都靠运气;

  • 对教程、课程录像、会议发言这类内容,用户真正要的从来不是"整片",而是"讲到 X 的那一小段"。

本插件补的就是这一段:公开视频 → 本地转写 → 片段级检索 → 可回跳的时间戳证据。全部本地完成,不依赖任何付费 API 或第三方检索服务。


工作流程

        ┌──────────────────────── dsh (MCP client) ────────────────────────┐
        │  mcp__video_evidence__video_search  /  __video_cache_stats       │
        └───────────────────────────────┬──────────────────────────────────┘
                                        │ stdio / JSON-RPC
        ┌───────────────────────────────▼──────────────────────────────────┐
        │  server.js   (Node 常驻进程,只做协议转发)                       │
        │   · 双分帧解析:NDJSON ⇄ Content-Length                           │
        │   · 收到 tools/call 才 spawn Python worker                        │
        └───────────────────────────────┬──────────────────────────────────┘
                                        │ spawn(按需,用完即退)
        ┌───────────────────────────────▼──────────────────────────────────┐
        │  worker.py → server.py                                           │
        │   1. bili.py      B 站 wbi 签名搜索 → 取 cid → 取 360P 流地址      │
        │   2. asr.py       ffmpeg 拉流转 16k 单声道 wav                     │
        │                   faster-whisper (base/int8, CPU) 转写            │
        │   3. cache.py     SQLite 按 BV 号缓存转写结果(TTL 30 天)          │
        │   4. evidence.py  中文 BM25(单字 + 二元组)打分                    │
        │                   45s 滑窗 / 30s 步长切块 → 排序取 top-k           │
        └──────────────────────────────────────────────────────────────────┘

为什么是 Node 常驻 + Python worker: dsh 的 dsh-mcp-client spawn 之后会立刻发 initialize,跟 Python 常驻进程握手不稳定(实测 4 次启动、0 条消息收到)。Node 冷启动足够快,而真正的检索/ASR 留在 Python 侧,只在工具被真正调用时才拉起,空闲时不占资源。


安装

前置依赖

依赖

说明

Node.js

18+,只用到内置模块,无需 npm install

Python

3.9+

ffmpeg

必须在 PATH 中,或用 FFMPEG_BIN 指定绝对路径

faster-whisper

pip install -r requirements.txt

git clone https://github.com/wjxn13/dsh-video-evidence.git
cd dsh-video-evidence

# Windows
python -m venv .venv
.venv\Scripts\pip install -r requirements.txt

# macOS / Linux
python3 -m venv .venv
# 注意:server.js 里写死了 .venv/Scripts/python.exe(Windows 布局)。
# 非 Windows 需要把 server.js 中 PY 的路径改成 .venv/bin/python。
.venv/bin/pip install -r requirements.txt

首次调用 video_search 时,faster-whisper 会自动下载 base 模型到 models/base/(约 145MB)。也可以手动放置,或用 ASR_MODEL_DIR 指向已有模型目录。

接入 dsh

在用户级 ~/.dsh/cordis.patch.yml 里追加(路径改成你自己的):

- insert:
    - id: mcp-video-evidence
      name: "@deepseek-ai/dsh-mcp-client"
      config:
        serverName: video_evidence        # ← 决定工具名前缀:mcp__video_evidence__*
        transport: stdio
        command: "C:/path/to/node.exe"
        args:
          - "C:/path/to/dsh-video-evidence/server.js"
        cwd: "C:/path/to/dsh-video-evidence"
        env:
          PYTHONIOENCODING: "utf-8"
          PYTHONUNBUFFERED: "1"
        toolCallTimeoutMs: 240000

几个关键点:

  • name 要填 @deepseek-ai/dsh-mcp-client,这是 dsh 的 MCP loader 包名,不是随便起的名字。

  • serverName 决定工具名前缀mcp__<serverName>__<toolName>,所以这里是 mcp__video_evidence__video_search

  • command 用 node 的绝对路径。dsh 会清理子进程环境,不能依赖 PATH。

  • cwd 要指向插件目录server.js 依赖它定位 .venvworker.py

  • toolCallTimeoutMs 给足(这里 240 秒)。首次查询要拉流 + 转写,默认的短超时会让工具在结果出来前被判失败。

然后重启 dsh——配置只在启动时加载。用 dsh --dump-config --profile web 可以确认条目有没有被正确合并进去。

⚠️ id 不能和已有条目重复。 cordis loader 不允许同 id 覆盖,重复会直接让 dsh 启动崩溃(报 duplicate loader entry)。改配置前先备份 cordis.patch.yml


工具

参数

类型

默认值

说明

query

string

必填

要检索的问题或关键词

max_videos

integer

3

最多处理几个候选视频(1–5)

max_clips

integer

5

返回几个最相关片段(1–10)

audio_seconds

integer

300

每个视频最多转写前多少秒(60–1800)

budget_seconds

integer

170

总时间预算,超时就返回已经拿到的结果

video_cache_stats

无参数。返回已缓存转写的视频数量。


性能实测

本机环境:Windows 11 / Ryzen 7 6800H / RTX 3050 Ti(纯 CPU 推理,不用显卡),faster-whisper base + int8

项目

实测

转写速度

45 秒音频 ≈ 2.3 秒(约 20 倍实时)

10 分钟视频

≈ 30 秒

命中缓存后

几乎瞬时(只查 SQLite + BM25)

也就是说,一次交互式提问(转写前 90–180 秒)通常在几秒内返回。


环境变量

变量

默认值

说明

FFMPEG_BIN

PATH 里的 ffmpeg

ffmpeg 可执行文件路径。不设则自动从 PATH 查找,找不到才需要显式指定

ASR_MODEL_DIR

<插件目录>/models/base

faster-whisper 模型目录

BILI_SESSDATA

B 站 SESSDATA Cookie。不填也能用(搜索/取流正常),填了可以尝试走官方字幕通道

VDB_CACHE

<插件目录>/cache.sqlite3

转写缓存数据库路径

VDB_CACHE_TTL

2592000(30 天)

缓存有效期(秒)

VDB_AUDIO_SEC

300

默认单视频转写时长上限

VDB_BUDGET_SEC

170

默认总时间预算

VDB_MAX_VIDEOS

3

默认候选视频数

VDB_WINDOW / VDB_STRIDE

45 / 30

滑窗切块的窗口长度与步长(秒)

VDB_WORKER_TIMEOUT

220000

Python worker 超时(毫秒)


已知限制

  • 只覆盖 B 站公开视频,且只转写前 audio_seconds 秒(默认 5 分钟)。长视频靠后的内容需要调大该参数,耗时线性增长。

  • 匿名状态下拿不到官方字幕subtitles 接口返回 0 条),所以一律走本地 ASR,不依赖 UP 主有没有上传字幕。

  • BM25 是词面匹配,不做语义向量检索。换个说法提问可能命中不到,这是有意的取舍——本地 CPU 上跑 embedding 模型会显著拖慢交互式响应。

  • server.js 里 Python 路径写死为 .venv/Scripts/python.exe(Windows 布局),非 Windows 需要手动改一处。


排错

工具注册不上去(dsh 里看不到 mcp__video_evidence__*

这是最坑的一类问题,现象是:配置明明正确(dsh --dump-config --profile web 能看到条目),重启也没用,日志里反复刷 server ready → stdin closed → server ready

根因:同一个 dsh 实例里存在两套 MCP 分帧格式。

  • dsh 自带的 dsh-mcp-client 发的是 NDJSON(裸 JSON + \n没有 Content-Length

  • argo / memorix 这类用标准 MCP SDK 的走的是 Content-Length: N\r\n\r\n{json}

只认 Content-Length 的解析器收 NDJSON 时永远凑不出一条完整消息 → 不回 initialize → dsh 超时关 stdin → 重拉,死循环。

本插件的 server.js 已经用 clientFraming 闩锁 + nextMessage() 做了双格式兼容,响应按客户端格式对称回写。如果你要 fork 改写协议层,这两段不要动

配套的两个必要细节:

  1. initialize 必须回显客户端发来的 protocolVersion,不能硬编码。MCP SDK 会校验服务端返回的版本是否在自身支持列表内,硬编码新版会让较老 SDK 判定协商失败并直接关闭 stdin。

  2. ping / resources/list / prompts/list 要返回空结果而不是 -32601 客户端探测能力时收到 method not found 会判定连接不健康,进而断连重连。

怎么定位: 在 stdin 的 data 回调里打第一条原始数据:

if (!gotAny) {
  gotAny = true;
  log("FIRST stdin data: raw=" + JSON.stringify(d.toString("utf8").slice(0, 400)));
}

看到 {"method":"initialize",...} 后面直接跟 \n、没有 Content-Length,就确认是 NDJSON。

B 站搜索返回空(HTTP 200、code:0、但结果为空)

wbi 签名的坑:urllib.parse.urlencode 默认用 quote_plus,空格编码成 +;B 站服务端按 %20 重算签名,校验不通过就静默拒绝。

# 必须用 quote(空格 -> %20),不能用默认的 quote_plus(空格 -> +)
q = urllib.parse.urlencode(p, quote_via=urllib.parse.quote)

bili.py 里已经修好,并且加了非 wbi 的 legacy 接口作为兜底。


目录结构

dsh-video-evidence/
├── server.js      # Node 常驻 MCP server(协议层:双分帧 + 方法分发)
├── worker.py      # Python worker 入口(CLI,读 JSON 参数、打 JSON 结果)
├── server.py      # 业务编排:搜索 → 取流 → 转写 → 检索
├── bili.py        # B 站 API 客户端(wbi 签名、搜索、cid、playurl)
├── asr.py         # ffmpeg 拉流 + faster-whisper 转写
├── evidence.py    # 中文 BM25 打分 + 滑窗切块
├── cache.py       # SQLite 转写缓存(按 BV 号,TTL 30 天)
└── requirements.txt

License

MIT

Maintenance

ActivityMaintained
ResponsivenessNo issues

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

Related MCP Servers

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/wjxn13/dsh-video-evidence'

If you have feedback or need assistance with the MCP directory API, please join our Discord server