ytdl-rmcp 0.7.1 → 0.7.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 (2) hide show
  1. package/README.md +361 -32
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,53 +1,382 @@
1
1
  # ytdl-rmcp
2
2
 
3
- Node launcher for the `ytdl-rmcp` Rust MCP server and CLI binary.
3
+ [![npm version](https://img.shields.io/npm/v/ytdl-rmcp.svg)](https://www.npmjs.com/package/ytdl-rmcp)
4
+ [![release](https://github.com/jmagar/ytdl-rmcp/actions/workflows/release.yml/badge.svg)](https://github.com/jmagar/ytdl-rmcp/actions/workflows/release.yml)
5
+ [![CI](https://github.com/jmagar/ytdl-rmcp/actions/workflows/ci.yml/badge.svg)](https://github.com/jmagar/ytdl-rmcp/actions/workflows/ci.yml)
4
6
 
5
- ```bash
6
- npx -y ytdl-rmcp
7
- ```
7
+ A cross-platform, single-binary **MCP server** that downloads media from any
8
+ [yt-dlp](https://github.com/yt-dlp/yt-dlp)-supported site (YouTube, Vimeo, …),
9
+ embeds metadata and cover art, organizes files by artist, and transfers the
10
+ result to a directory on an SSH remote — over `rsync` (with an `scp` fallback for
11
+ hosts that lack it, e.g. Windows).
12
+
13
+ Written in Rust on the [`rmcp`](https://crates.io/crates/rmcp) crate. **yt-dlp
14
+ and ffmpeg are auto-downloaded** into a per-user cache on first run, so the host
15
+ needs neither pre-installed — the one binary is the whole install.
16
+
17
+ ---
18
+
19
+ ## Features
20
+
21
+ - **Audio, video, or both** — audio-first by default, with separate remote
22
+ destinations for audio and video.
23
+ - **Proper tagging** — embeds title / artist / album / date and cover art, and
24
+ organizes output as `Artist/Title [id].ext` so media servers (Plex, etc.)
25
+ index it cleanly. A non-greedy `Artist - Title` parse recovers the artist from
26
+ free-form video titles. Source `.info.json`, thumbnail, and description
27
+ sidecars are preserved next to the media for future retagging/indexing.
28
+ Common YouTube title noise like `(Official Video)`, `[Official Audio]`, and
29
+ trailing channel handles is stripped from embedded title metadata by default.
30
+ - **Self-contained paths** — the binary downloads/caches yt-dlp + ffmpeg when
31
+ run directly; the container image bakes in ffmpeg, fpcalc, SSH, and rsync for
32
+ media-host batch jobs.
33
+ - **Self-installing** — `ytdl-rmcp setup` registers the server into Claude Code,
34
+ Codex, and/or Gemini CLI via each tool's own `mcp add`.
35
+ - **Robust transfers** — `rsync --protect-args` when present, `scp` otherwise;
36
+ non-interactive SSH (`BatchMode=yes`, `StrictHostKeyChecking=accept-new`) so a
37
+ TTY-less server never hangs on a prompt. On transfer failure the local staging
38
+ copy is kept for retry.
39
+ - **Repeat-safe** — `use_archive` records downloaded IDs (per mode) and skips
40
+ them on later runs; YouTube mix/radio URLs are auto-cleaned to the seed video.
41
+ - **Stats-ready ledger** — every completed download call appends a JSONL entry
42
+ with timestamp, destinations, files, bytes, uploader, and transfer status.
43
+ - **Plex playlist sync** — when Plex credentials are configured, downloaded
44
+ audio is added to `yt-dlp Downloads` by default.
45
+
46
+ ## Tools
47
+
48
+ | Tool | Purpose |
49
+ | --- | --- |
50
+ | `youtube_search` | Search YouTube with yt-dlp and return result URLs without downloading. |
51
+ | `youtube_search_ui` | Open an interactive YouTube search UI in MCP App-capable hosts. |
52
+ | `youtube_download` | Download one or more URLs (audio/video/both) and rsync/scp them to a remote dir. |
53
+ | `youtube_probe` | Read-only: resolve title/duration/uploader/format counts without downloading. |
54
+ | `youtube_identify` | Fingerprint local audio with `fpcalc`, return AcoustID/MusicBrainz candidates, preview canonical tags, and optionally write high-confidence tags. |
55
+ | `youtube_stats` | Summarize the download ledger: totals, file kinds, uploaders, and recent entries. |
56
+
57
+ ### `youtube_download` parameters
58
+
59
+ | Param | Default | Meaning |
60
+ | --- | --- | --- |
61
+ | `urls` | — (required) | One URL string or an array of URLs. |
62
+ | `mode` | `audio` | `audio`, `video`, or `both`. |
63
+ | `audio_format` | env `YTDLP_AUDIO_FORMAT` → `mp3` | `mp3`/`m4a`/`opus`/`flac`/`wav`/`best`. |
64
+ | `audio_quality` | `0` | yt-dlp quality for lossy codecs: `0`–`9` or a bitrate like `192K`. |
65
+ | `max_height` | best | Cap video resolution (e.g. `1080`). |
66
+ | `container` | `mp4` | `mp4` or `mkv` for video. |
67
+ | `remote` | env `YTDLP_REMOTE` | SSH alias or `user@host`. |
68
+ | `dest_path` | env `YTDLP_REMOTE_PATH` | Absolute remote dir for audio. |
69
+ | `video_dest_path` | env `YTDLP_VIDEO_REMOTE_PATH` → `dest_path` | Absolute remote dir for video. |
70
+ | `keep_local` | `false` | Keep the local staging copy after transfer. |
71
+ | `use_archive` | `false` | Record + skip already-downloaded IDs (per mode). |
72
+ | `plex_playlist` | env `YTDLP_PLEX_PLAYLIST` → `yt-dlp Downloads` when Plex is configured | Plex playlist title or ID to add downloaded audio tracks to. Requires `YTDLP_PLEX_URL` and `YTDLP_PLEX_TOKEN`. |
73
+ | `response_format` | `markdown` | `markdown` or `json`. |
74
+
75
+ When Plex credentials are configured, successful downloads that produced audio
76
+ files search Plex for each downloaded track, create the target playlist if
77
+ needed, and add missing tracks while skipping entries already present. The
78
+ default playlist is `yt-dlp Downloads`; set `YTDLP_PLEX_PLAYLIST` or pass
79
+ `plex_playlist` to override it. Plex errors are reported as
80
+ `plex_playlist_error` and do not make the completed download fail. JSON
81
+ responses include a `plex_playlist` summary with `matched`, `added`,
82
+ `already_present`, and `missing` counts.
83
+
84
+ Canonical metadata matching through MusicBrainz/AcoustID is documented in
85
+ `docs/musicbrainz-acoustid.md`. `youtube_download` automatically runs
86
+ high-confidence MusicBrainz retagging for downloaded audio when
87
+ `YTDLP_ACOUSTID_CLIENT_KEY` is configured; `youtube_identify` remains available
88
+ for previewing or repairing existing library files, with manual tag writes
89
+ enabled by `write_tags=true`.
90
+
91
+ #### `youtube_download` JSON response
92
+
93
+ With `response_format=json`, the call returns a single object describing the
94
+ batch:
95
+
96
+ | Field | Meaning |
97
+ | --- | --- |
98
+ | `transferred` | `true` if every produced subtree reached the remote. |
99
+ | `transfer_error` | `null` on success, else the failure/timeout message (string). |
100
+ | `remote` / `dest_path` / `destination` / `destinations` | The remote and the per-kind destination(s) actually used. |
101
+ | `staging_kept_at` | Local staging path retained for retry (set when the transfer failed or `keep_local` was requested). |
102
+ | `total_files` / `total_bytes` / `total_size` | Aggregate counts across all items. |
103
+ | `partial_items` | Count of items that errored **but** still produced files. |
104
+ | `failed_items` | Count of items that errored **and** produced no files. |
105
+ | `items[]` | Per-URL results, each with a `status`, `title`, `video_id`, `error`, and a `files[]` list. |
106
+
107
+ Each `items[].status` is one of:
108
+
109
+ - `ok` — succeeded with files.
110
+ - `partial` — an error occurred but some files were still produced.
111
+ - `failed` — errored with no files.
112
+ - `skipped` — nothing new (already in the archive).
8
113
 
9
- Run the guided setup:
114
+ Optional keys are attached only when the relevant stage ran:
115
+
116
+ - `metadata_retag` — MusicBrainz/AcoustID auto-retag summary (`attempted`,
117
+ `matched`, `written`, `skipped`, `errors`, or an `error` string); present when
118
+ `YTDLP_ACOUSTID_CLIENT_KEY` is configured.
119
+ - `plex_playlist` — Plex playlist summary (`playlist`, `matched`, `added`,
120
+ `already_present`, `missing`); `plex_playlist_error` is set instead if the
121
+ Plex update failed (a Plex failure does not fail the download).
122
+ - `history_error` — set when the download succeeded but the JSONL ledger append
123
+ failed.
124
+
125
+ `youtube_probe` takes `urls` and `response_format`.
126
+
127
+ ### `youtube_identify` parameters
128
+
129
+ | Param | Default | Meaning |
130
+ | --- | --- | --- |
131
+ | `paths` | — (required) | One local audio file path string or an array of paths. |
132
+ | `write_tags` | `false` | Write high-confidence MusicBrainz tag previews back to the audio files. |
133
+ | `response_format` | `markdown` | `markdown` or `json`. |
134
+
135
+ `youtube_identify` runs Chromaprint `fpcalc`, sends the fingerprint to AcoustID,
136
+ and returns MusicBrainz recording candidates. When the best candidate is
137
+ high-confidence, it also fetches the MusicBrainz recording/release data and
138
+ includes a `retag_preview` showing the canonical artist, title, release, release
139
+ date, release type, track number, and MusicBrainz IDs. By default it is
140
+ preview-only. With `write_tags=true`, it writes the preview to the file with
141
+ Lofty, including common title/artist/album/date/track fields plus MusicBrainz
142
+ recording, release, release-group, and release-type tags. It requires
143
+ `YTDLP_ACOUSTID_CLIENT_KEY`; set `FPCALC_PATH` if `fpcalc` is not on `PATH`.
144
+
145
+ ### `youtube_search` parameters
146
+
147
+ | Param | Default | Meaning |
148
+ | --- | --- | --- |
149
+ | `query` | - (required) | YouTube search text. The server passes this to yt-dlp as `ytsearchN:<query>`. |
150
+ | `limit` | `10` | Number of results, clamped to `1..=25`. |
151
+ | `response_format` | `markdown` | `markdown` or `json`. |
152
+
153
+ `youtube_search_ui` accepts the same input and returns the same search payload,
154
+ plus MCP App metadata for hosts that can render the embedded UI.
155
+
156
+ ### `youtube_stats` parameters
157
+
158
+ | Param | Default | Meaning |
159
+ | --- | --- | --- |
160
+ | `limit` | `10` | Number of recent ledger entries to include, clamped to `0..=100`. |
161
+ | `response_format` | `markdown` | `markdown` or `json`. |
162
+
163
+ JSON stats include `total_downloads`, `total_files`, `total_bytes`,
164
+ `skipped_entries`, `by_kind`, `by_uploader`, and `recent`. Bucket fields include
165
+ `downloads` (compatibility alias for call count), `calls`, `items`, `files`,
166
+ `bytes`, and human-readable `size`. Malformed ledger lines are skipped and
167
+ counted instead of failing the whole stats call. If a download succeeds but the
168
+ ledger append fails, the download response still succeeds and includes
169
+ `history_error` in JSON output.
170
+
171
+ ## Install
172
+
173
+ Run the guided installer through npm:
10
174
 
11
175
  ```bash
12
176
  npx -y ytdl-rmcp setup
13
177
  ```
14
178
 
15
- Install globally when you want the command on `PATH`:
179
+ Or install the command globally:
16
180
 
17
181
  ```bash
18
182
  npm i -g ytdl-rmcp
19
- ytdl-rmcp --version
20
183
  ytdl-rmcp setup
21
184
  ```
22
185
 
23
- The package downloads the matching GitHub Release binary during `postinstall`.
24
- The npm package version and the `ytdl-rmcp` release tag are expected to match.
25
- Release automation publishes this package from the repository `v*` tag workflow;
26
- the GitHub repository must have an `NPM_TOKEN` secret with publish access.
27
-
28
- ## MCP stdio
29
-
30
- Run without subcommands, `ytdl-rmcp` serves MCP over stdio. MCP clients can launch
31
- it with:
32
-
33
- ```json
34
- {
35
- "command": "npx",
36
- "args": ["-y", "ytdl-rmcp"],
37
- "env": {
38
- "YTDLP_REMOTE": "tootie",
39
- "YTDLP_REMOTE_PATH": "/media/music"
40
- }
41
- }
186
+ The npm package downloads the matching GitHub Release binary during
187
+ `postinstall`; the installed command is the Rust binary served through a tiny
188
+ Node launcher. You can also use the one-line installer:
189
+
190
+ ```bash
191
+ curl -fsSL https://raw.githubusercontent.com/jmagar/ytdl-rmcp/main/scripts/install.sh | bash
192
+ ```
193
+
194
+ Or download the binary tarball for your platform from
195
+ [Releases](https://github.com/jmagar/ytdl-rmcp/releases), or build it (see below).
196
+ The guided setup fetches yt-dlp + ffmpeg, prompts for your SSH remote and
197
+ audio/video destinations, detects which agent CLIs are present, and registers
198
+ the server into the ones you pick.
199
+
200
+ ### Manual registration
201
+
202
+ Run without subcommands, `npx -y ytdl-rmcp` serves MCP over stdio. Register it
203
+ yourself:
204
+
205
+ ```bash
206
+ # Claude Code
207
+ claude mcp add -s user ytdl-rmcp -e YTDLP_REMOTE=tootie -e YTDLP_REMOTE_PATH=/media/music -- npx -y ytdl-rmcp
208
+ # Codex
209
+ codex mcp add --env YTDLP_REMOTE=tootie --env YTDLP_REMOTE_PATH=/media/music ytdl-rmcp -- npx -y ytdl-rmcp
210
+ # Gemini CLI (command is positional, env last)
211
+ gemini mcp add -s user ytdl-rmcp npx -y ytdl-rmcp -e YTDLP_REMOTE=tootie -e YTDLP_REMOTE_PATH=/media/music
212
+ ```
213
+
214
+ If you already installed a standalone binary with `npm i -g ytdl-rmcp`,
215
+ `scripts/install.sh`, or a release tarball, you can use that binary path in
216
+ place of `npx -y ytdl-rmcp`.
217
+
218
+ ### Distributed forms
219
+
220
+ - **npm launcher** — `npx -y ytdl-rmcp` downloads and runs the matching
221
+ GitHub Release binary. Run without subcommands, it serves MCP over stdio;
222
+ `npx -y ytdl-rmcp setup` runs the guided installer. Stable releases publish
223
+ the package from GitHub Actions with npm provenance.
224
+ - **Claude Code plugin** — `.claude-plugin/plugin.json` prompts for config via
225
+ `userConfig`; `.mcp.json` launches `npx -y ytdl-rmcp`, which downloads the
226
+ matching GitHub Release binary through npm.
227
+ - **Gemini CLI extension** — `gemini-extension.json`; install with
228
+ `gemini extensions install https://github.com/jmagar/ytdl-rmcp`. MCP clients
229
+ should prefer the npm launcher command, `npx -y ytdl-rmcp`.
230
+ - **Container image** — `ghcr.io/jmagar/ytdl-rmcp:main` is published on every
231
+ push to `main`, or build locally with `docker build -t ytdl-rmcp:local .`. It
232
+ includes `ffmpeg`, `fpcalc`, `openssh-client`, and `rsync`. See
233
+ [`docs/container.md`](docs/container.md) for MCP and mounted-library examples.
234
+ - **MCP bundle (`.mcpb` / `.dxt`)** — `mcpb/manifest.json` defines a
235
+ `binary`-type bundle for one-click install in MCPB-capable desktop hosts.
236
+ Every main release publishes `ytdl-rmcp.mcpb` plus a legacy `ytdl-rmcp.dxt`
237
+ alias; both contain the same linux + windows binaries. The bundle defaults
238
+ optional config values to empty strings so Claude Desktop can install it
239
+ before you fill in the SSH destination settings. Configure at least the
240
+ remote and audio destination in the extension settings before downloading.
241
+ Build one locally from prebuilt binaries with `scripts/build-mcpb.sh` (needs
242
+ Node for the `@anthropic-ai/mcpb` CLI).
243
+
244
+ ## Configuration (environment variables)
245
+
246
+ | Var | Default | Meaning |
247
+ | --- | --- | --- |
248
+ | `YTDLP_REMOTE` | — | SSH remote (alias or `user@host`) for transfers. |
249
+ | `YTDLP_REMOTE_PATH` | — | Absolute remote dir for **audio**. |
250
+ | `YTDLP_VIDEO_REMOTE_PATH` | falls back to audio | Absolute remote dir for **video**. |
251
+ | `YTDLP_AUDIO_FORMAT` | `mp3` | Default audio codec. |
252
+ | `YTDLP_STAGING_DIR` | system temp | Local staging base dir. |
253
+ | `YTDLP_SSH_OPTS` | — | Extra ssh options parsed with shell-word syntax; appended after the forced `BatchMode`/`StrictHostKeyChecking` flags. Example: `-i "~/.ssh/ytdl key" -o ProxyJump=media-bastion`. Malformed quoting is rejected. |
254
+ | `YTDLP_ARCHIVE_DIR` | per-user state dir | Where `use_archive` history lives. |
255
+ | `YTDLP_HISTORY_PATH` | per-user state dir `downloads.jsonl` | JSONL download ledger used by `youtube_stats`. |
256
+ | `YTDLP_PLEX_URL` | — | Plex server URL, e.g. `http://plex.local:32400`, used when adding audio downloads to a Plex playlist. |
257
+ | `YTDLP_PLEX_TOKEN` | — | Plex auth token for playlist/search API calls. |
258
+ | `YTDLP_PLEX_PLAYLIST` | `yt-dlp Downloads` when Plex URL/token are set | Default Plex playlist title or ID for `youtube_download`; can be overridden with the `plex_playlist` parameter. |
259
+ | `YTDLP_CLEAN_METADATA` | `1` | Strip common YouTube title noise before embedding metadata. Set to `0` to preserve source titles exactly. |
260
+ | `YTDLP_ACOUSTID_CLIENT_KEY` | — | AcoustID application API key required by `youtube_identify`; when set, `youtube_download` automatically writes high-confidence MusicBrainz tags to downloaded audio before transfer. |
261
+ | `FPCALC_PATH` | `fpcalc` on `PATH` | Optional explicit path to the Chromaprint `fpcalc` executable. |
262
+ | `YTDLP_MUSICBRAINZ_CONTACT` | GitHub repo URL | Contact URL/email included in lookup User-Agent strings. |
263
+ | `YTDLP_AUTO_UPDATE` | `1` | Re-download yt-dlp when stale. |
264
+ | `YTDLP_MAX_AGE_DAYS` | `14` | Staleness threshold (days). |
265
+ | `YTDLP_UPDATE_PRE` | `0` | Track yt-dlp's nightly channel. |
266
+ | `YTDLP_EXTRACTOR_ARGS` | — | Passed to yt-dlp `--extractor-args`, e.g. `youtube:player_client=android` for videos the default clients can't reach. |
267
+ | `YTDLP_TIMEOUT_SECS` | `1800` | Timeout for each yt-dlp probe/download command. |
268
+ | `YTDLP_TRANSFER_TIMEOUT_SECS` | `600` | Timeout for each transfer phase. |
269
+ | `YTDLP_SHA256` / `FFMPEG_SHA256` | — | Optional SHA-256 digest required for the resolved yt-dlp / ffmpeg executable. |
270
+ | `YTDLP_PATH` / `FFMPEG_PATH` | — | Use a specific yt-dlp / ffmpeg instead of auto-download. |
271
+ | `YTDLP_LOG` | `info` | `tracing` filter (stderr only). |
272
+
273
+ > **Maintainers:** this table is maintained **by hand**. `scripts/check-packaging.sh`
274
+ > only cross-checks the four machine-readable config surfaces (the Claude plugin
275
+ > `.mcp.json` / `userConfig`, `gemini-extension.json`, and `mcpb/manifest.json`);
276
+ > it does **not** read this README. When you add, rename, or remove a `YTDLP_*`
277
+ > env var, update this table manually — nothing in CI will catch the drift.
278
+
279
+ ### Bootstrap trust model
280
+
281
+ By default, first run resolves tools in this order: explicit env override,
282
+ `PATH`, cache, then HTTPS download from the upstream release source. Set
283
+ `YTDLP_SHA256` and/or `FFMPEG_SHA256` to require an exact executable digest
284
+ after resolution or download. These pins verify bytes on disk, but they do not
285
+ fetch upstream signatures or automatically discover trusted digests; operators
286
+ who need a fully pinned supply chain should provide known-good binaries through
287
+ `YTDLP_PATH` / `FFMPEG_PATH` plus matching SHA-256 pins, or disable yt-dlp
288
+ auto-update.
289
+
290
+ For stricter bootstrap control, combine `YTDLP_PATH` / `FFMPEG_PATH` with
291
+ matching `YTDLP_SHA256` / `FFMPEG_SHA256` pins. Hash pins verify the resolved
292
+ executable bytes; they are not upstream signature verification.
293
+
294
+ ### Security posture
295
+
296
+ This server is designed to run with **trusted callers and operator-supplied
297
+ config** — it is not a hardened multi-tenant boundary.
298
+
299
+ - **Tool-call URLs reach yt-dlp.** Whatever `urls` an MCP caller passes are
300
+ handed to yt-dlp, a powerful extraction tool. Only point callers at it that
301
+ you trust. Tool-call URLs are validated as `http`/`https` before they reach
302
+ yt-dlp, and every positional is passed after a `--` end-of-options separator so
303
+ a `-`-prefixed value can't be parsed as a flag; the trust assumption above
304
+ still holds regardless.
305
+ - **SSH is key-only and non-interactive.** Transfers force `BatchMode=yes` and
306
+ `StrictHostKeyChecking=accept-new`, so a TTY-less server fails fast instead of
307
+ prompting; there is no password auth. Auth comes from your SSH key/agent and
308
+ any options you add via `YTDLP_SSH_OPTS`.
309
+ - **Remote specs and paths are validated.** The `RemoteSpec` and `RemotePath`
310
+ newtypes reject empty values, option-like (`-`-leading) remotes, and
311
+ whitespace/control characters before any value reaches `ssh`/`rsync`/`scp`,
312
+ and remote paths are single-quote-escaped for the remote shell.
313
+
314
+ ## Requirements
315
+
316
+ - **ssh** (and optionally **rsync** — falls back to **scp**, e.g. on Windows).
317
+ - Passwordless key-based SSH auth to the remote.
318
+ - yt-dlp and ffmpeg are fetched automatically (override with `YTDLP_PATH` /
319
+ `FFMPEG_PATH`, or just have them on `PATH`).
320
+ - `youtube_identify` additionally needs `fpcalc`; the container image includes
321
+ it via `libchromaprint-tools`.
322
+
323
+ ## Build from source
324
+
325
+ ```bash
326
+ cargo build --release # Linux/macOS
327
+ cargo test && cargo clippy --all-targets -- -D warnings # checks
328
+
329
+ # Cross-compile to Windows from Linux (needs nasm + the LLVM toolchain):
330
+ sudo apt-get install -y nasm llvm clang lld
331
+ cargo install cargo-xwin
332
+ cargo xwin build --release --target x86_64-pc-windows-msvc
42
333
  ```
43
334
 
44
- ## Overrides
335
+ On dookie/local shells, `~/.local/bin/cargo` is a wrapper that can break
336
+ `cargo xwin`; use the real rustup cargo for local Windows rehearsals:
45
337
 
46
338
  ```bash
47
- YTDL_RMCP_BINARY_VERSION=v0.7.0 npm i -g ytdl-rmcp
48
- YTDL_RMCP_RELEASE_BASE_URL=https://github.com/jmagar/ytdl-rmcp/releases/download npm i -g ytdl-rmcp
49
- YTDL_RMCP_SKIP_DOWNLOAD=1 npm i -g ytdl-rmcp
339
+ ~/.cargo/bin/cargo xwin build --release --target x86_64-pc-windows-msvc
50
340
  ```
51
341
 
52
- Supported binary targets are Linux x64 and Windows x64, matching the current
53
- GitHub Release assets.
342
+ CI (`.github/workflows/`) runs fmt + clippy + tests and a Windows cross-build on
343
+ every push/PR, and publishes both binaries to a GitHub Release on `v*` tags.
344
+
345
+ This crate intentionally remains on Rust edition 2021 for the distributable
346
+ single-binary/plugin build. Move to edition 2024 only after proving Linux,
347
+ Windows MSVC cross-build, and plugin startup compatibility together.
348
+
349
+ ## How it works
350
+
351
+ Bare invocation serves MCP over stdio; `setup` runs the installer. A
352
+ `youtube_download` call:
353
+
354
+ 1. Resolves yt-dlp + ffmpeg (env override → PATH → cache → download) and
355
+ verifies SHA-256 pins when configured.
356
+ 2. Cleans mix/radio URLs, then runs yt-dlp per mode into a staging tree
357
+ (`staging/audio`, `staging/video`) with metadata/thumbnail/archive flags,
358
+ source metadata sidecars, and the `Artist/Title [id]` output template.
359
+ 3. *(optional)* When `YTDLP_ACOUSTID_CLIENT_KEY` is set, fingerprints the
360
+ downloaded audio and writes high-confidence MusicBrainz/AcoustID tags
361
+ in-place — before transfer, so the remote copy carries the canonical tags.
362
+ 4. Transfers each kind's subtree to its own remote dir (rsync, else scp).
363
+ 5. *(optional)* When Plex credentials are configured and the transfer
364
+ succeeded, adds the downloaded audio tracks to the target Plex playlist.
365
+ 6. Appends the completed call to the JSONL download ledger.
366
+ 7. Returns a markdown or JSON summary listing files, sizes, and the actual
367
+ destination(s).
368
+
369
+ See `CLAUDE.md` for architecture, conventions, and gotchas.
370
+
371
+ ## License
372
+
373
+ MIT — see `LICENSE`.
374
+
375
+ ## Rust MCP naming pattern
376
+
377
+ This repo follows the Rust MCP server naming convention:
378
+
379
+ - Repo: `ytdl-rmcp`
380
+ - CLI alias: `rytdl`
381
+ - npm package: `ytdl-rmcp`
382
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ytdl-rmcp",
3
- "version": "0.7.1",
3
+ "version": "0.7.2",
4
4
  "description": "Node launcher for the ytdl-rmcp Rust MCP server and CLI binary.",
5
5
  "license": "MIT",
6
6
  "repository": {