@sylphx/video-reader-mcp 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +229 -41
  2. package/bin/native/video-reader-mcp-server +0 -0
  3. package/bin/video-reader-mcp +48 -0
  4. package/dist/doctor-cli.js +167 -0
  5. package/package.json +23 -10
  6. package/src/doctor-cli.ts +18 -0
  7. package/src/doctor.ts +132 -0
  8. package/src/engine/rust-asr.ts +98 -0
  9. package/src/engine/rust-frames.ts +95 -0
  10. package/src/engine/rust-timeline.ts +164 -0
  11. package/src/engine/rust-video-evidence.ts +132 -0
  12. package/src/handlers/readVideo.ts +41 -0
  13. package/src/handlers/videoEvidence.ts +161 -0
  14. package/src/mcp.ts +302 -0
  15. package/src/schemas/readVideo.ts +79 -0
  16. package/src/schemas/videoEvidence.ts +53 -0
  17. package/src/sdk.ts +44 -0
  18. package/src/types/timeline.ts +119 -0
  19. package/src/utils/agentIndex.ts +107 -0
  20. package/src/utils/asr.ts +59 -0
  21. package/src/utils/errors.ts +16 -0
  22. package/src/utils/exec.ts +76 -0
  23. package/src/utils/ffmpeg.ts +81 -0
  24. package/src/utils/ffprobe.ts +137 -0
  25. package/src/utils/frameOcr.ts +99 -0
  26. package/src/utils/frames.ts +93 -0
  27. package/src/utils/pathUtils.ts +36 -0
  28. package/src/utils/scenes.ts +63 -0
  29. package/src/utils/structuralKeyframes.ts +73 -0
  30. package/src/utils/subtitles.ts +101 -0
  31. package/src/video/readCoordinator.ts +240 -0
  32. package/dist/handlers/readVideo.js +0 -24
  33. package/dist/index.js +0 -59
  34. package/dist/mcp.js +0 -181
  35. package/dist/schemas/readVideo.js +0 -33
  36. package/dist/types/timeline.js +0 -1
  37. package/dist/utils/asr.js +0 -26
  38. package/dist/utils/errors.js +0 -14
  39. package/dist/utils/exec.js +0 -56
  40. package/dist/utils/ffmpeg.js +0 -66
  41. package/dist/utils/ffprobe.js +0 -79
  42. package/dist/utils/pathUtils.js +0 -31
  43. package/dist/utils/scenes.js +0 -49
  44. package/dist/utils/subtitles.js +0 -85
  45. package/dist/video/readCoordinator.js +0 -81
package/README.md CHANGED
@@ -1,86 +1,274 @@
1
- # Video Reader MCP
1
+ <div align="center">
2
2
 
3
- > Evidence-first video reading for AI agents — ffprobe, subtitles, scenes, transcripts, and timelines without frame-by-frame LLM vision.
3
+ # Cue
4
4
 
5
- **Status:** v0.1.0 shipped `read_video` MCP tool available.
5
+ ### Timeline proof for agents
6
6
 
7
- Orchestrated by [smart-reader-mcp](https://github.com/SylphxAI/smart-reader-mcp) — portfolio ADR lives there, not in pdf-reader-mcp.
7
+ **Cue** (transitional package `@sylphx/video-reader-mcp`) — local video timeline evidence, not frame-by-frame VLM.
8
8
 
9
- | Repository | Role |
9
+ <p align="center">
10
+ <img src="https://mark.sylphx.com/api/v1/banner?type=mesh&theme=tokyonight&text=video+reader+mcp&desc=Evidence-first+video+reading+for+AI+agents+%E2%80%94+ffprobe%2C+subtitles%2C+scenes%2C+transcr&height=200&animation=rise&credit=0" alt="video-reader-mcp — Sylphx Mark banner" width="100%" />
11
+ </p>
12
+
13
+ ### Your agent watched the video. **Did it read the timeline?**
14
+
15
+ Evidence-first video reading for AI agents. One call turns any local video into a
16
+ **timeline document** — ffprobe streams, chapters, embedded subtitles, scene
17
+ boundaries, and warnings you can cite without frame-by-frame vision LLM calls.
18
+
19
+ [![npm version](https://img.shields.io/npm/v/@sylphx/video-reader-mcp?style=flat-square)](https://www.npmjs.com/package/@sylphx/video-reader-mcp)
20
+ [![License](https://img.shields.io/badge/License-MIT-blue?style=flat-square)](https://opensource.org/licenses/MIT)
21
+ [![CI/CD](https://img.shields.io/github/actions/workflow/status/SylphxAI/video-reader-mcp/ci.yml?style=flat-square&label=CI/CD)](https://github.com/SylphxAI/video-reader-mcp/actions/workflows/ci.yml)
22
+ [![TypeScript](https://img.shields.io/badge/TypeScript-7.0-blue.svg?style=flat-square)](https://www.typescriptlang.org/)
23
+
24
+ **Local-first** · **One smart `read_video` call** · **Timeline evidence + provenance** · **20 tests**
25
+
26
+ SOTA family roadmap: [docs/roadmap/sota-family-roadmap.md](docs/roadmap/sota-family-roadmap.md).
27
+
28
+ [⭐ Star this repo](https://github.com/SylphxAI/video-reader-mcp) if agents should read video timelines with proof, not sampled frame captions.
29
+ · [Quick start](#quick-start) · [See it work](#see-it-work) · [Why not frame-by-frame vision?](#why-not-frame-by-frame-vision)
30
+
31
+ This repository is product SSOT. Sibling agent tools live in separate repos.
32
+ Each instrument is an independent repository (marketplace + stars).
33
+
34
+ </div>
35
+
36
+ ---
37
+
38
+
39
+
40
+ ## Read video structure (not N-second frame spam)
41
+
42
+ Cue is **local-first timeline architecture**: streams, dialogue, scene cuts, **structural keyframes**, and **agent_index** for text-only agents.
43
+
44
+ Spec: [docs/specs/agent-video-read-contract.md](docs/specs/agent-video-read-contract.md)
45
+
46
+ ## Product docs
47
+
48
+ | Doc | Purpose |
10
49
  | --- | --- |
11
- | [pdf-reader-mcp](https://github.com/SylphxAI/pdf-reader-mcp) | PDF (production) |
12
- | [image-reader-mcp](https://github.com/SylphxAI/image-reader-mcp) | Image |
13
- | **video-reader-mcp** (this repo) | Video |
14
- | [smart-reader-mcp](https://github.com/SylphxAI/smart-reader-mcp) | Unified read + delegate |
50
+ | [docs/POSITIONING.md](docs/POSITIONING.md) | Strategic positioning |
51
+ | [docs/COMPETITIVE.md](docs/COMPETITIVE.md) | Peer anchors and wedge |
52
+ | [docs/EVIDENCE_CONTRACT.md](docs/EVIDENCE_CONTRACT.md) | Evidence = result contract |
53
+ | [docs/TOOL_SURFACE.md](docs/TOOL_SURFACE.md) | Few clear tools policy |
54
+ | [docs/PRODUCT_INDEPENDENCE.md](docs/PRODUCT_INDEPENDENCE.md) | This repo is SSOT |
55
+ | [docs/IPPB.md](docs/IPPB.md) | Independent public product bar |
56
+ | [docs/PUBLISH.md](docs/PUBLISH.md) | npm/git publish status |
57
+
58
+ ## The problem
15
59
 
16
- ## Read vs interpret
60
+ Videos are not a pile of frames. They are codecs, chapters, embedded subtitles,
61
+ scene cuts, variable frame rates, and timelines measured in milliseconds.
17
62
 
18
- **Read** (this repo): extract facts, metadata, transcripts, regions, and timelines with provenance **no generative LLM required**.
63
+ Most agent stacks sample frames and ask a **vision LLM** what it sees. Subtitles
64
+ get skipped. Scene boundaries vanish. Duration and stream metadata never reach
65
+ context. Citations become "around minute two, I think." Then the agent
66
+ hallucinates — confidently.
19
67
 
20
- **Interpret** (out of scope): summarize, classify, or answer open questions — belongs in the agent or an optional remote provider adapter.
68
+ **Video Reader MCP is built for the moment your agent needs a citeable timeline,
69
+ not a slideshow summary.**
21
70
 
22
- ## MCP surface
71
+ ## Why not frame-by-frame vision?
23
72
 
24
- Primary tool: `read_video`
73
+ | Typical vision path | Video Reader MCP |
74
+ | --- | --- |
75
+ | Sample N frames into a vision model | ffprobe format + stream metadata in one call |
76
+ | Paraphrased "what happens" | Embedded subtitle cues with `start_ms`, `end_ms`, and provenance |
77
+ | Scene changes guessed from captions | Optional ffmpeg scene filter with timestamp evidence |
78
+ | Missing audio / VFR silently ignored | Warnings for missing ffmpeg/ffprobe, VFR, missing audio, skipped ASR |
79
+ | Cloud API by default | **Local-first** — ffprobe + ffmpeg on your machine |
80
+ | Ship and pray | **20** tests on parsers, fixture corpus, doctor, release gate, and integration |
25
81
 
26
- Returns a **timeline document** per source:
82
+ ## See it work
27
83
 
28
- - ffprobe format + stream metadata
29
- - chapter markers
30
- - embedded subtitle cues (`time_ms`, text, provenance)
31
- - optional scene boundaries (ffmpeg `scene` filter)
32
- - warnings (missing ffmpeg/ffprobe, VFR, missing audio, skipped ASR)
33
- - optional local ASR hook (skipped in v0.1 unless adapter is wired)
84
+ ### Install (30 seconds)
34
85
 
35
- No per-frame vision LLM calls. No cloud APIs by default.
86
+ ```bash
87
+ npm install -g @sylphx/video-reader-mcp
88
+ cue doctor
89
+ claude mcp add cue -- npx @sylphx/video-reader-mcp
90
+ ```
91
+
92
+ **Install once. Call once.**
93
+
94
+ ```bash
95
+ claude mcp add video-reader -- npx @sylphx/video-reader-mcp
96
+ ```
97
+
98
+ ```json
99
+ {
100
+ "sources": [{ "path": "/absolute/path/to/demo.mp4" }],
101
+ "include_subtitles": true,
102
+ "include_scenes": true
103
+ }
104
+ ```
105
+
106
+ `read_video` builds a timeline document per source — no per-frame vision LLM
107
+ calls:
108
+
109
+ ```json
110
+ {
111
+ "source": "/absolute/path/to/demo.mp4",
112
+ "success": true,
113
+ "data": {
114
+ "provenance": {
115
+ "source": "/absolute/path/to/demo.mp4",
116
+ "tool": "read_video",
117
+ "version": "0.1.0",
118
+ "extracted_at": "2026-07-09T12:00:00.000Z"
119
+ },
120
+ "format": {
121
+ "format_name": "mov,mp4,m4a,3gp,3g2,mj2",
122
+ "duration_ms": 125500
123
+ },
124
+ "streams": [
125
+ { "index": 0, "codec_type": "video", "width": 1920, "height": 1080 },
126
+ { "index": 1, "codec_type": "audio", "channels": 2, "sample_rate": 48000 }
127
+ ],
128
+ "chapters": [
129
+ { "id": 0, "start_ms": 0, "end_ms": 60250, "title": "Intro" }
130
+ ],
131
+ "subtitles": [
132
+ {
133
+ "index": 0,
134
+ "start_ms": 1200,
135
+ "end_ms": 3400,
136
+ "text": "Welcome to the demo.",
137
+ "provenance": { "method": "ffmpeg_extract", "format": "srt" }
138
+ }
139
+ ],
140
+ "scenes": [
141
+ {
142
+ "index": 0,
143
+ "time_ms": 45200,
144
+ "provenance": { "method": "ffmpeg_scene_filter", "threshold": 0.4 }
145
+ }
146
+ ],
147
+ "warnings": []
148
+ }
149
+ }
150
+ ```
151
+
152
+ Abbreviated shape — optional local ASR transcript hooks skip gracefully when no
153
+ adapter is wired.
36
154
 
37
155
  ## Prerequisites
38
156
 
39
- - Node.js 22.13
157
+ - Node.js `>=22.13`
40
158
  - **ffprobe** (required) and **ffmpeg** (recommended for subtitles + scenes) on `PATH`
41
159
 
42
- ## Quick start
160
+ ## MCP Tool Surface
43
161
 
44
- ```bash
45
- npx @sylphx/video-reader-mcp
46
- ```
162
+ | Tool | Use it when the agent needs to... |
163
+ | --- | --- |
164
+ | `read_video` | Read one or more local videos and return ffprobe metadata, chapters, subtitles, scenes, and timeline warnings. |
165
+
166
+ Supported formats: MP4, M4V, MKV, MOV, WebM, and other formats ffprobe can inspect.
47
167
 
48
- From source:
168
+ ## Quick Start
169
+
170
+ ### Claude Code
49
171
 
50
172
  ```bash
51
- bun install
52
- bun run build
53
- bun run start
173
+ claude mcp add video-reader -- npx @sylphx/video-reader-mcp
54
174
  ```
55
175
 
56
- ### Example `read_video` input
176
+ ### Claude Desktop
177
+
178
+ Add this to `claude_desktop_config.json`:
57
179
 
58
180
  ```json
59
181
  {
60
- "sources": [{ "path": "./sample.mp4" }],
61
- "include_subtitles": true,
62
- "include_scenes": true,
63
- "scene_threshold": 0.4
182
+ "mcpServers": {
183
+ "video-reader": {
184
+ "command": "npx",
185
+ "args": ["@sylphx/video-reader-mcp"]
186
+ }
187
+ }
64
188
  }
65
189
  ```
66
190
 
191
+ ### Any MCP Client
192
+
193
+ ```bash
194
+ npx @sylphx/video-reader-mcp
195
+ ```
196
+
67
197
  ### HTTP transport (optional)
68
198
 
69
199
  ```bash
70
- MCP_TRANSPORT=http MCP_HTTP_PORT=8080 node dist/index.js
200
+ MCP_TRANSPORT=http MCP_HTTP_PORT=8080 npx @sylphx/video-reader-mcp
201
+ ```
202
+
203
+ ## Security model
204
+
205
+ - **Local-first** — `read_video` inspects local files; remote URLs are not fetched by default.
206
+ - **ffprobe/ffmpeg boundary** — probe and frame tools shell out to configured binaries on PATH; missing tools return explicit errors.
207
+ - **Fixture corpus** — CI validates parser and safety fixtures; corrupted inputs fail closed with structured diagnostics.
208
+ - **Evidence envelope** — timestamps, frame indices, and extraction routes are preserved so agents can verify claims.
209
+
210
+ ## Release proof
211
+
212
+ Claims are backed by CI `benchmark:release-gate`, fixture corpus checks, and the shipped-path matrix (Rust-default primary tools).
213
+
214
+ ```bash
215
+ bun run benchmark:release-gate
71
216
  ```
72
217
 
218
+ Artifact: `benchmark-artifacts/video_reader_release_gate.json` — must report `status: passed` before release.
219
+
73
220
  ## Development
74
221
 
75
222
  ```bash
223
+ git clone https://github.com/SylphxAI/video-reader-mcp.git
224
+ cd video-reader-mcp
76
225
  bun install
77
- bun run typecheck
78
- bun test
79
226
  bun run build
227
+ bun test
228
+ bun run doctor
229
+ bun run benchmark:release-gate
230
+ ```
231
+
232
+ Useful checks:
233
+
234
+ ```bash
235
+ bun run check
236
+ bun run typecheck
237
+ bun run benchmark:release-gate
80
238
  ```
81
239
 
82
- Unit tests mock parsers and do not require ffmpeg in CI. Integration with real media is optional locally.
240
+ Example `read_video` requests live in [`examples/`](examples/). CI runs parser,
241
+ fixture corpus, doctor, and release-gate checks; integration tests exercise ffmpeg
242
+ when available on the runner.
243
+
244
+ ## Support
245
+
246
+ - [Issues](https://github.com/SylphxAI/video-reader-mcp/issues)
247
+ - [npm package](https://www.npmjs.com/package/@sylphx/video-reader-mcp)
248
+ - Portfolio orchestration: [smart-reader-mcp](https://github.com/SylphxAI/smart-reader-mcp)
249
+
250
+ ## Help this reach more builders
251
+
252
+ If frame-by-frame vision guesses have wasted your context, your citations, or
253
+ your trust in agent output, you are exactly who this project is for.
254
+
255
+ **[⭐ Star the repo](https://github.com/SylphxAI/video-reader-mcp)** — it is the
256
+ fastest way to help more agent builders find evidence-first video reading. Share
257
+ it in your MCP client setup, team wiki, or agent stack README.
258
+
259
+ ### Discovery (in progress)
260
+
261
+ | Channel | Status |
262
+ | --- | --- |
263
+ | [Glama MCP directory](https://glama.ai/mcp/servers/SylphxAI/video-reader-mcp) | Listed — [claim server](https://glama.ai/mcp/servers/SylphxAI/video-reader-mcp/admin) for full discoverability |
264
+ | [Official MCP Registry](https://registry.modelcontextprotocol.io/v0.1/servers?search=io.github.SylphxAI/video-reader-mcp) | Listed — `io.github.SylphxAI/video-reader-mcp` @ v0.1.0 |
265
+ | [TensorBlock MCP Index PR #1113](https://github.com/TensorBlock/awesome-mcp-servers/pull/1113) | Open — multimedia/document processing listing |
266
+ | [MCP servers community issue #4500](https://github.com/modelcontextprotocol/servers/issues/4500) | Open — community server highlight |
267
+ | [mcp.so listing issue #3068](https://github.com/chatmcp/mcpso/issues/3068) | Open — directory submission request |
268
+ | [mcpservers.org submit](https://mcpservers.org/submit) | Not listed yet — free web-form submission |
269
+
270
+ Know another MCP directory? [Open an issue](https://github.com/SylphxAI/video-reader-mcp/issues/new) with the link.
83
271
 
84
272
  ## License
85
273
 
86
- MIT © [SylphxAI](https://github.com/SylphxAI)
274
+ MIT © [SylphxAI](https://github.com/SylphxAI)
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env bash
2
+ # Video Reader MCP launcher — Rust rmcp only (fail-closed, no TS stdio fallback).
3
+ set -euo pipefail
4
+
5
+ ROOT="$(cd "$(dirname "$0")/.." && pwd)"
6
+
7
+ resolve_rust_bin() {
8
+ if [[ -n "${VIDEO_READER_MCP_RUST_BIN:-}" && -x "${VIDEO_READER_MCP_RUST_BIN}" ]]; then
9
+ printf '%s\n' "${VIDEO_READER_MCP_RUST_BIN}"
10
+ return 0
11
+ fi
12
+
13
+ for candidate in \
14
+ "$ROOT/bin/native/video-reader-mcp-server" \
15
+ "$ROOT/target/release/video-reader-mcp-server" \
16
+ "$ROOT/target/debug/video-reader-mcp-server"; do
17
+ if [[ -x "$candidate" ]]; then
18
+ printf '%s\n' "$candidate"
19
+ return 0
20
+ fi
21
+ done
22
+
23
+ return 1
24
+ }
25
+
26
+ resolve_transport() {
27
+ if [[ -n "${VIDEO_READER_MCP_TRANSPORT:-}" ]]; then
28
+ printf '%s\n' "${VIDEO_READER_MCP_TRANSPORT}"
29
+ return 0
30
+ fi
31
+ if [[ -n "${MCP_TRANSPORT:-}" ]]; then
32
+ printf '%s\n' "${MCP_TRANSPORT}"
33
+ return 0
34
+ fi
35
+ printf '%s\n' "stdio"
36
+ }
37
+
38
+ if bin="$(resolve_rust_bin)"; then
39
+ transport="$(resolve_transport)"
40
+ if [[ "$transport" == "http" ]]; then
41
+ export MCP_TRANSPORT=http
42
+ export VIDEO_READER_MCP_TRANSPORT=http
43
+ fi
44
+ exec "$bin" "$@"
45
+ fi
46
+
47
+ echo "[video-reader-mcp] Rust MCP server (rmcp) is not built. Run: bun run build:rust" >&2
48
+ exit 1
@@ -0,0 +1,167 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/doctor-cli.ts
4
+ import { createRequire } from "node:module";
5
+
6
+ // src/doctor.ts
7
+ import { spawnSync } from "node:child_process";
8
+ import { existsSync as existsSync2 } from "node:fs";
9
+ import path2 from "node:path";
10
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
11
+
12
+ // src/engine/rust-timeline.ts
13
+ import { existsSync } from "node:fs";
14
+ import path from "node:path";
15
+ import { fileURLToPath } from "node:url";
16
+ var here = path.dirname(fileURLToPath(import.meta.url));
17
+ function resolveRustCliBinary() {
18
+ const env = process.env.VIDEO_READER_CLI;
19
+ if (env && existsSync(env)) {
20
+ return env;
21
+ }
22
+ const release = path.join(here, "../../target/release/video-reader-cli");
23
+ if (existsSync(release)) {
24
+ return release;
25
+ }
26
+ const debug = path.join(here, "../../target/debug/video-reader-cli");
27
+ if (existsSync(debug)) {
28
+ return debug;
29
+ }
30
+ return "video-reader-cli";
31
+ }
32
+
33
+ // src/utils/exec.ts
34
+ import { execFile } from "node:child_process";
35
+ import { promisify } from "node:util";
36
+ var execFileAsync = promisify(execFile);
37
+ var DEFAULT_MAX_BUFFER = 16 * 1024 * 1024;
38
+ var binaryCache = new Map;
39
+ var isBinaryAvailable = async (binary) => {
40
+ const cached = binaryCache.get(binary);
41
+ if (cached !== undefined)
42
+ return cached;
43
+ try {
44
+ await execFileAsync(binary, ["-version"], { timeout: 5000, encoding: "utf8" });
45
+ binaryCache.set(binary, true);
46
+ return true;
47
+ } catch {
48
+ try {
49
+ await execFileAsync(binary, ["--version"], { timeout: 5000, encoding: "utf8" });
50
+ binaryCache.set(binary, true);
51
+ return true;
52
+ } catch {
53
+ binaryCache.set(binary, false);
54
+ return false;
55
+ }
56
+ }
57
+ };
58
+
59
+ // src/doctor.ts
60
+ var here2 = path2.dirname(fileURLToPath2(import.meta.url));
61
+ var probeBinary = async (id, binary, required) => {
62
+ const available = await isBinaryAvailable(binary);
63
+ if (available) {
64
+ return {
65
+ id,
66
+ status: "ok",
67
+ message: `${binary} is installed and responds to -version.`
68
+ };
69
+ }
70
+ return {
71
+ id,
72
+ status: required ? "fail" : "warn",
73
+ message: required ? `${binary} is required for read_video but was not found on PATH.` : `${binary} is optional for some extraction paths.`
74
+ };
75
+ };
76
+ var probeRustTimelineCli = () => {
77
+ const binary = resolveRustCliBinary();
78
+ if (binary !== "video-reader-cli" && existsSync2(binary)) {
79
+ const probe = spawnSync(binary, [], {
80
+ input: JSON.stringify({
81
+ tool: "build_cache_key",
82
+ input: {
83
+ source_hash: "abc123",
84
+ options: { include_streams: true, include_chapters: true }
85
+ }
86
+ }),
87
+ encoding: "utf8",
88
+ timeout: 5000
89
+ });
90
+ if (probe.status === 0) {
91
+ return {
92
+ id: "rust_timeline_cli",
93
+ status: "ok",
94
+ message: `Rust timeline CLI is available at ${binary}.`
95
+ };
96
+ }
97
+ }
98
+ const release = path2.join(here2, "../target/release/video-reader-cli");
99
+ const debug = path2.join(here2, "../target/debug/video-reader-cli");
100
+ if (existsSync2(release) || existsSync2(debug)) {
101
+ return {
102
+ id: "rust_timeline_cli",
103
+ status: "ok",
104
+ message: "Rust timeline CLI is built locally."
105
+ };
106
+ }
107
+ return {
108
+ id: "rust_timeline_cli",
109
+ status: "warn",
110
+ message: "Rust timeline CLI is not built. Run `cargo build --release` to enable VIDEO_READER_USE_RUST_TIMELINE=1."
111
+ };
112
+ };
113
+ var probeNode = () => {
114
+ const version = process.versions.node;
115
+ const major = Number.parseInt(version.split(".")[0] ?? "0", 10);
116
+ if (major >= 22) {
117
+ return {
118
+ id: "node",
119
+ status: "ok",
120
+ message: `Node.js ${version} meets the >=22.13 requirement.`
121
+ };
122
+ }
123
+ return {
124
+ id: "node",
125
+ status: "warn",
126
+ message: `Node.js ${version} is below the recommended >=22.13 runtime.`
127
+ };
128
+ };
129
+ var aggregateStatus = (checks) => {
130
+ if (checks.some((check) => check.status === "fail")) {
131
+ return "unavailable";
132
+ }
133
+ if (checks.some((check) => check.status === "warn")) {
134
+ return "degraded";
135
+ }
136
+ return "ready";
137
+ };
138
+ async function runDoctor(version) {
139
+ const checks = [
140
+ probeNode(),
141
+ probeRustTimelineCli(),
142
+ await probeBinary("ffprobe", "ffprobe", true),
143
+ await probeBinary("ffmpeg", "ffmpeg", false)
144
+ ];
145
+ return {
146
+ profile: "video_reader_doctor",
147
+ version,
148
+ status: aggregateStatus(checks),
149
+ checks
150
+ };
151
+ }
152
+ function formatDoctorReport(report) {
153
+ return JSON.stringify(report, null, 2);
154
+ }
155
+
156
+ // src/doctor-cli.ts
157
+ var require2 = createRequire(import.meta.url);
158
+ var packageJson = require2("../package.json");
159
+ async function main() {
160
+ const report = await runDoctor(packageJson.version);
161
+ console.log(formatDoctorReport(report));
162
+ process.exit(report.status === "unavailable" ? 1 : 0);
163
+ }
164
+ main().catch((error) => {
165
+ console.error("[Video Reader MCP] Doctor error:", error);
166
+ process.exit(1);
167
+ });
package/package.json CHANGED
@@ -1,23 +1,28 @@
1
1
  {
2
2
  "name": "@sylphx/video-reader-mcp",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "mcpName": "io.github.SylphxAI/video-reader-mcp",
5
- "description": "Evidence-first video reading for AI agents ffprobe, subtitles, scenes, transcripts, and timelines without frame-by-frame LLM vision.",
5
+ "description": "Cue \u2014 Evidence-first video reading for AI agents \u2014 ffprobe, subtitles, scenes, transcripts, and timelines without frame-by-frame LLM vision.",
6
6
  "type": "module",
7
7
  "publishConfig": {
8
8
  "access": "public"
9
9
  },
10
10
  "license": "MIT",
11
11
  "bin": {
12
- "video-reader-mcp": "./dist/index.js"
12
+ "video-reader-mcp": "./bin/video-reader-mcp",
13
+ "cue": "./bin/video-reader-mcp"
13
14
  },
14
15
  "files": [
16
+ "bin/",
15
17
  "dist/",
16
18
  "README.md",
17
- "LICENSE"
19
+ "LICENSE",
20
+ "src/"
18
21
  ],
19
22
  "exports": {
20
- ".": "./dist/index.js"
23
+ ".": "./dist/doctor-cli.js",
24
+ "./sdk": "./src/sdk.ts",
25
+ "./cue": "./src/sdk.ts"
21
26
  },
22
27
  "repository": {
23
28
  "type": "git",
@@ -39,17 +44,24 @@
39
44
  "node": ">=22.13.0"
40
45
  },
41
46
  "scripts": {
42
- "build": "tsc && chmod +x dist/index.js",
47
+ "build:rust": "cargo build --release -p video-reader-core -p video-reader-cli -p video-reader-mcp-server && bun scripts/stage-rust-mcp.ts",
48
+ "test:rust": "cargo test",
49
+ "build": "bun build src/doctor-cli.ts --outdir dist --target node && chmod +x dist/doctor-cli.js",
43
50
  "typecheck": "tsc --noEmit -p tsconfig.json && tsc --noEmit -p tsconfig.test.json",
44
51
  "test": "bun test",
52
+ "test:rust-timeline": "cargo test && bun test test/rustTimeline.boundary.test.ts",
45
53
  "check": "biome check .",
46
54
  "check:fix": "biome check --write .",
47
55
  "sync:server-json": "bun scripts/sync-server-json.ts",
48
56
  "version-packages": "changeset version && bun run sync:server-json",
49
- "start": "node dist/index.js",
57
+ "start": "./bin/video-reader-mcp",
50
58
  "clean": "rm -rf dist coverage",
51
- "prepublishOnly": "bun run clean && bun run build",
52
- "release": "bun run typecheck && bun run check && bun run build && bun test && changeset publish"
59
+ "prepublishOnly": "bun run clean && bun run build && bun run build:rust",
60
+ "release": "bun run typecheck && bun run check && bun run build && bun test && changeset publish",
61
+ "doctor": "bun run src/doctor-cli.ts",
62
+ "benchmark:release-gate": "bun scripts/release-gate.ts",
63
+ "benchmark:public-proof": "bun scripts/public-proof.ts",
64
+ "brand:pack-plan": "bun scripts/brand-pack-plan.ts"
53
65
  },
54
66
  "dependencies": {
55
67
  "@modelcontextprotocol/sdk": "^1.29.0",
@@ -63,5 +75,6 @@
63
75
  "@types/node": "^25.9.1",
64
76
  "typescript": "^7.0.2"
65
77
  },
66
- "packageManager": "bun@1.3.12"
78
+ "packageManager": "bun@1.3.12",
79
+ "private": false
67
80
  }
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { createRequire } from 'node:module';
4
+ import { formatDoctorReport, runDoctor } from './doctor.js';
5
+
6
+ const require = createRequire(import.meta.url);
7
+ const packageJson = require('../package.json') as { version: string };
8
+
9
+ async function main(): Promise<void> {
10
+ const report = await runDoctor(packageJson.version);
11
+ console.log(formatDoctorReport(report));
12
+ process.exit(report.status === 'unavailable' ? 1 : 0);
13
+ }
14
+
15
+ main().catch((error: unknown) => {
16
+ console.error('[Video Reader MCP] Doctor error:', error);
17
+ process.exit(1);
18
+ });