@sylphx/video-reader-mcp 0.1.0 → 0.1.1

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 (43) hide show
  1. package/README.md +222 -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 +67 -0
  16. package/src/schemas/videoEvidence.ts +53 -0
  17. package/src/sdk.ts +44 -0
  18. package/src/types/timeline.ts +106 -0
  19. package/src/utils/asr.ts +59 -0
  20. package/src/utils/errors.ts +16 -0
  21. package/src/utils/exec.ts +76 -0
  22. package/src/utils/ffmpeg.ts +81 -0
  23. package/src/utils/ffprobe.ts +137 -0
  24. package/src/utils/frameOcr.ts +99 -0
  25. package/src/utils/frames.ts +93 -0
  26. package/src/utils/pathUtils.ts +36 -0
  27. package/src/utils/scenes.ts +63 -0
  28. package/src/utils/subtitles.ts +101 -0
  29. package/src/video/readCoordinator.ts +165 -0
  30. package/dist/handlers/readVideo.js +0 -24
  31. package/dist/index.js +0 -59
  32. package/dist/mcp.js +0 -181
  33. package/dist/schemas/readVideo.js +0 -33
  34. package/dist/types/timeline.js +0 -1
  35. package/dist/utils/asr.js +0 -26
  36. package/dist/utils/errors.js +0 -14
  37. package/dist/utils/exec.js +0 -56
  38. package/dist/utils/ffmpeg.js +0 -66
  39. package/dist/utils/ffprobe.js +0 -79
  40. package/dist/utils/pathUtils.js +0 -31
  41. package/dist/utils/scenes.js +0 -49
  42. package/dist/utils/subtitles.js +0 -85
  43. package/dist/video/readCoordinator.js +0 -81
package/README.md CHANGED
@@ -1,86 +1,267 @@
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
+ ## Product docs
40
+
41
+ | Doc | Purpose |
10
42
  | --- | --- |
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 |
43
+ | [docs/POSITIONING.md](docs/POSITIONING.md) | Strategic positioning |
44
+ | [docs/COMPETITIVE.md](docs/COMPETITIVE.md) | Peer anchors and wedge |
45
+ | [docs/EVIDENCE_CONTRACT.md](docs/EVIDENCE_CONTRACT.md) | Evidence = result contract |
46
+ | [docs/TOOL_SURFACE.md](docs/TOOL_SURFACE.md) | Few clear tools policy |
47
+ | [docs/PRODUCT_INDEPENDENCE.md](docs/PRODUCT_INDEPENDENCE.md) | This repo is SSOT |
48
+ | [docs/IPPB.md](docs/IPPB.md) | Independent public product bar |
49
+ | [docs/PUBLISH.md](docs/PUBLISH.md) | npm/git publish status |
15
50
 
16
- ## Read vs interpret
51
+ ## The problem
17
52
 
18
- **Read** (this repo): extract facts, metadata, transcripts, regions, and timelines with provenance — **no generative LLM required**.
53
+ Videos are not a pile of frames. They are codecs, chapters, embedded subtitles,
54
+ scene cuts, variable frame rates, and timelines measured in milliseconds.
19
55
 
20
- **Interpret** (out of scope): summarize, classify, or answer open questions belongs in the agent or an optional remote provider adapter.
56
+ Most agent stacks sample frames and ask a **vision LLM** what it sees. Subtitles
57
+ get skipped. Scene boundaries vanish. Duration and stream metadata never reach
58
+ context. Citations become "around minute two, I think." Then the agent
59
+ hallucinates — confidently.
21
60
 
22
- ## MCP surface
61
+ **Video Reader MCP is built for the moment your agent needs a citeable timeline,
62
+ not a slideshow summary.**
23
63
 
24
- Primary tool: `read_video`
64
+ ## Why not frame-by-frame vision?
25
65
 
26
- Returns a **timeline document** per source:
66
+ | Typical vision path | Video Reader MCP |
67
+ | --- | --- |
68
+ | Sample N frames into a vision model | ffprobe format + stream metadata in one call |
69
+ | Paraphrased "what happens" | Embedded subtitle cues with `start_ms`, `end_ms`, and provenance |
70
+ | Scene changes guessed from captions | Optional ffmpeg scene filter with timestamp evidence |
71
+ | Missing audio / VFR silently ignored | Warnings for missing ffmpeg/ffprobe, VFR, missing audio, skipped ASR |
72
+ | Cloud API by default | **Local-first** — ffprobe + ffmpeg on your machine |
73
+ | Ship and pray | **20** tests on parsers, fixture corpus, doctor, release gate, and integration |
27
74
 
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)
75
+ ## See it work
34
76
 
35
- No per-frame vision LLM calls. No cloud APIs by default.
77
+ ### Install (30 seconds)
78
+
79
+ ```bash
80
+ npm install -g @sylphx/video-reader-mcp
81
+ cue doctor
82
+ claude mcp add cue -- npx @sylphx/video-reader-mcp
83
+ ```
84
+
85
+ **Install once. Call once.**
86
+
87
+ ```bash
88
+ claude mcp add video-reader -- npx @sylphx/video-reader-mcp
89
+ ```
90
+
91
+ ```json
92
+ {
93
+ "sources": [{ "path": "/absolute/path/to/demo.mp4" }],
94
+ "include_subtitles": true,
95
+ "include_scenes": true
96
+ }
97
+ ```
98
+
99
+ `read_video` builds a timeline document per source — no per-frame vision LLM
100
+ calls:
101
+
102
+ ```json
103
+ {
104
+ "source": "/absolute/path/to/demo.mp4",
105
+ "success": true,
106
+ "data": {
107
+ "provenance": {
108
+ "source": "/absolute/path/to/demo.mp4",
109
+ "tool": "read_video",
110
+ "version": "0.1.0",
111
+ "extracted_at": "2026-07-09T12:00:00.000Z"
112
+ },
113
+ "format": {
114
+ "format_name": "mov,mp4,m4a,3gp,3g2,mj2",
115
+ "duration_ms": 125500
116
+ },
117
+ "streams": [
118
+ { "index": 0, "codec_type": "video", "width": 1920, "height": 1080 },
119
+ { "index": 1, "codec_type": "audio", "channels": 2, "sample_rate": 48000 }
120
+ ],
121
+ "chapters": [
122
+ { "id": 0, "start_ms": 0, "end_ms": 60250, "title": "Intro" }
123
+ ],
124
+ "subtitles": [
125
+ {
126
+ "index": 0,
127
+ "start_ms": 1200,
128
+ "end_ms": 3400,
129
+ "text": "Welcome to the demo.",
130
+ "provenance": { "method": "ffmpeg_extract", "format": "srt" }
131
+ }
132
+ ],
133
+ "scenes": [
134
+ {
135
+ "index": 0,
136
+ "time_ms": 45200,
137
+ "provenance": { "method": "ffmpeg_scene_filter", "threshold": 0.4 }
138
+ }
139
+ ],
140
+ "warnings": []
141
+ }
142
+ }
143
+ ```
144
+
145
+ Abbreviated shape — optional local ASR transcript hooks skip gracefully when no
146
+ adapter is wired.
36
147
 
37
148
  ## Prerequisites
38
149
 
39
- - Node.js 22.13
150
+ - Node.js `>=22.13`
40
151
  - **ffprobe** (required) and **ffmpeg** (recommended for subtitles + scenes) on `PATH`
41
152
 
42
- ## Quick start
153
+ ## MCP Tool Surface
43
154
 
44
- ```bash
45
- npx @sylphx/video-reader-mcp
46
- ```
155
+ | Tool | Use it when the agent needs to... |
156
+ | --- | --- |
157
+ | `read_video` | Read one or more local videos and return ffprobe metadata, chapters, subtitles, scenes, and timeline warnings. |
47
158
 
48
- From source:
159
+ Supported formats: MP4, M4V, MKV, MOV, WebM, and other formats ffprobe can inspect.
160
+
161
+ ## Quick Start
162
+
163
+ ### Claude Code
49
164
 
50
165
  ```bash
51
- bun install
52
- bun run build
53
- bun run start
166
+ claude mcp add video-reader -- npx @sylphx/video-reader-mcp
54
167
  ```
55
168
 
56
- ### Example `read_video` input
169
+ ### Claude Desktop
170
+
171
+ Add this to `claude_desktop_config.json`:
57
172
 
58
173
  ```json
59
174
  {
60
- "sources": [{ "path": "./sample.mp4" }],
61
- "include_subtitles": true,
62
- "include_scenes": true,
63
- "scene_threshold": 0.4
175
+ "mcpServers": {
176
+ "video-reader": {
177
+ "command": "npx",
178
+ "args": ["@sylphx/video-reader-mcp"]
179
+ }
180
+ }
64
181
  }
65
182
  ```
66
183
 
184
+ ### Any MCP Client
185
+
186
+ ```bash
187
+ npx @sylphx/video-reader-mcp
188
+ ```
189
+
67
190
  ### HTTP transport (optional)
68
191
 
69
192
  ```bash
70
- MCP_TRANSPORT=http MCP_HTTP_PORT=8080 node dist/index.js
193
+ MCP_TRANSPORT=http MCP_HTTP_PORT=8080 npx @sylphx/video-reader-mcp
194
+ ```
195
+
196
+ ## Security model
197
+
198
+ - **Local-first** — `read_video` inspects local files; remote URLs are not fetched by default.
199
+ - **ffprobe/ffmpeg boundary** — probe and frame tools shell out to configured binaries on PATH; missing tools return explicit errors.
200
+ - **Fixture corpus** — CI validates parser and safety fixtures; corrupted inputs fail closed with structured diagnostics.
201
+ - **Evidence envelope** — timestamps, frame indices, and extraction routes are preserved so agents can verify claims.
202
+
203
+ ## Release proof
204
+
205
+ Claims are backed by CI `benchmark:release-gate`, fixture corpus checks, and the shipped-path matrix (Rust-default primary tools).
206
+
207
+ ```bash
208
+ bun run benchmark:release-gate
71
209
  ```
72
210
 
211
+ Artifact: `benchmark-artifacts/video_reader_release_gate.json` — must report `status: passed` before release.
212
+
73
213
  ## Development
74
214
 
75
215
  ```bash
216
+ git clone https://github.com/SylphxAI/video-reader-mcp.git
217
+ cd video-reader-mcp
76
218
  bun install
77
- bun run typecheck
78
- bun test
79
219
  bun run build
220
+ bun test
221
+ bun run doctor
222
+ bun run benchmark:release-gate
223
+ ```
224
+
225
+ Useful checks:
226
+
227
+ ```bash
228
+ bun run check
229
+ bun run typecheck
230
+ bun run benchmark:release-gate
80
231
  ```
81
232
 
82
- Unit tests mock parsers and do not require ffmpeg in CI. Integration with real media is optional locally.
233
+ Example `read_video` requests live in [`examples/`](examples/). CI runs parser,
234
+ fixture corpus, doctor, and release-gate checks; integration tests exercise ffmpeg
235
+ when available on the runner.
236
+
237
+ ## Support
238
+
239
+ - [Issues](https://github.com/SylphxAI/video-reader-mcp/issues)
240
+ - [npm package](https://www.npmjs.com/package/@sylphx/video-reader-mcp)
241
+ - Portfolio orchestration: [smart-reader-mcp](https://github.com/SylphxAI/smart-reader-mcp)
242
+
243
+ ## Help this reach more builders
244
+
245
+ If frame-by-frame vision guesses have wasted your context, your citations, or
246
+ your trust in agent output, you are exactly who this project is for.
247
+
248
+ **[⭐ Star the repo](https://github.com/SylphxAI/video-reader-mcp)** — it is the
249
+ fastest way to help more agent builders find evidence-first video reading. Share
250
+ it in your MCP client setup, team wiki, or agent stack README.
251
+
252
+ ### Discovery (in progress)
253
+
254
+ | Channel | Status |
255
+ | --- | --- |
256
+ | [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 |
257
+ | [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 |
258
+ | [TensorBlock MCP Index PR #1113](https://github.com/TensorBlock/awesome-mcp-servers/pull/1113) | Open — multimedia/document processing listing |
259
+ | [MCP servers community issue #4500](https://github.com/modelcontextprotocol/servers/issues/4500) | Open — community server highlight |
260
+ | [mcp.so listing issue #3068](https://github.com/chatmcp/mcpso/issues/3068) | Open — directory submission request |
261
+ | [mcpservers.org submit](https://mcpservers.org/submit) | Not listed yet — free web-form submission |
262
+
263
+ Know another MCP directory? [Open an issue](https://github.com/SylphxAI/video-reader-mcp/issues/new) with the link.
83
264
 
84
265
  ## License
85
266
 
86
- MIT © [SylphxAI](https://github.com/SylphxAI)
267
+ 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.1",
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
+ });